"""Incoming provider webhooks — delivery updates and status callbacks."""
from __future__ import annotations

import hashlib
import hmac
import json
from typing import Any
from uuid import UUID

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings
from app.events.publisher import get_event_publisher
from app.events.types import CommunicationEventType
from app.models.foundation import Message, WebhookReceipt
from app.models.types import MessageStatus
from app.repositories.foundation import MessageRepository, WebhookReceiptRepository
from app.services.router import CommunicationRouter
from app.validators import validate_webhook
from shared.exceptions import AppError


class WebhookService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = WebhookReceiptRepository(session)
        self.messages = MessageRepository(session)
        self.router = CommunicationRouter(session)
        self.events = get_event_publisher()

    def verify_signature(
        self,
        *,
        payload: dict[str, Any] | None,
        signature: str | None,
        secret: str | None,
        raw_body: str | None = None,
    ) -> bool:
        if not secret:
            # Dev/test only — production should configure webhook secrets
            return settings.environment in ("development", "test")
        if not signature:
            return False
        body = raw_body if raw_body is not None else json.dumps(
            payload or {}, sort_keys=True, separators=(",", ":")
        )
        digest = hmac.new(secret.encode(), body.encode(), hashlib.sha256).hexdigest()
        return hmac.compare_digest(digest, signature)

    async def receive(
        self,
        tenant_id: UUID,
        data: dict[str, Any],
        *,
        webhook_secret: str | None = None,
        raw_body: str | None = None,
    ) -> WebhookReceipt:
        validate_webhook(data)
        external_id = data.get("external_id")
        if external_id:
            existing = await self._find_by_external(
                tenant_id, data["provider_kind"], external_id
            )
            if existing:
                return existing

        signature_valid = self.verify_signature(
            payload=data.get("payload"),
            signature=data.get("signature"),
            secret=webhook_secret,
            raw_body=raw_body,
        )
        if webhook_secret and not signature_valid:
            raise AppError(
                "Invalid webhook signature",
                status_code=401,
                error_code="webhook_signature_invalid",
            )

        receipt = WebhookReceipt(
            tenant_id=tenant_id,
            provider_kind=data["provider_kind"],
            event_type=data["event_type"],
            external_id=external_id,
            message_id=data.get("message_id"),
            payload=data.get("payload"),
            signature_valid=signature_valid,
            processed=False,
        )
        await self.repo.add(receipt)
        self.events.publish(
            event_type=CommunicationEventType.WEBHOOK_RECEIVED,
            aggregate_type="webhook_receipt",
            aggregate_id=receipt.id,
            tenant_id=tenant_id,
            payload={
                "event_type": data["event_type"],
                "provider_kind": data["provider_kind"],
                "external_id": external_id,
            },
        )

        if signature_valid:
            try:
                await self._apply(tenant_id, receipt, data)
                receipt.processed = True
            except Exception as exc:  # noqa: BLE001
                receipt.processing_error = str(exc)
        await self.session.commit()
        await self.session.refresh(receipt)
        return receipt

    async def _find_by_external(
        self, tenant_id: UUID, provider_kind: str, external_id: str
    ) -> WebhookReceipt | None:
        stmt = select(WebhookReceipt).where(
            WebhookReceipt.tenant_id == tenant_id,
            WebhookReceipt.provider_kind == provider_kind,
            WebhookReceipt.external_id == external_id,
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def _apply(
        self, tenant_id: UUID, receipt: WebhookReceipt, data: dict[str, Any]
    ) -> None:
        message_id = data.get("message_id")
        payload = data.get("payload") or {}
        if not message_id and payload.get("provider_message_id"):
            stmt = select(Message).where(
                Message.tenant_id == tenant_id,
                Message.provider_message_id == payload["provider_message_id"],
                Message.is_deleted.is_(False),
            )
            result = await self.session.execute(stmt)
            message = result.scalar_one_or_none()
            if message:
                message_id = message.id
                receipt.message_id = message.id

        if not message_id:
            return

        event_type = data["event_type"].lower()
        if event_type in ("delivered", "delivery", "message.delivered"):
            await self.router.mark_delivered(
                tenant_id, message_id, detail="webhook delivery"
            )
        elif event_type in ("failed", "failure", "message.failed"):
            message = await self.messages.get(tenant_id, message_id)
            if message:
                message.status = MessageStatus.FAILED.value
                message.error_message = payload.get("error") or "webhook_failed"
                await self.session.flush()
        elif event_type in ("expired", "message.expired"):
            message = await self.messages.get(tenant_id, message_id)
            if message:
                message.status = MessageStatus.EXPIRED.value
                await self.session.flush()
