"""Webhook utilities.

Shared webhook functionality for both Hatchet and Celery pipelines.
"""

import hashlib
import hmac
import uuid
from datetime import datetime, timezone
from typing import Union

import httpx
from pydantic import BaseModel

from reflector.logger import logger
from reflector.settings import settings
from reflector.utils.string import NonEmptyString
from reflector.utils.webhook_outgoing_models import (
    WebhookCalendarEventPayload,
    WebhookParticipantPayload,
    WebhookPayload,
    WebhookRoomPayload,
    WebhookTestPayload,
    WebhookTopicPayload,
    WebhookTranscriptPayload,
)

__all__ = [
    "fetch_transcript_webhook_payload",
    "fetch_test_webhook_payload",
    "build_webhook_headers",
    "generate_webhook_signature",
    "send_webhook_request",
]

from reflector.db.calendar_events import calendar_events_controller
from reflector.db.meetings import meetings_controller
from reflector.db.rooms import rooms_controller
from reflector.db.transcripts import transcripts_controller
from reflector.utils.webvtt import topics_to_webvtt


def _serialize_payload(payload: BaseModel) -> bytes:
    """Serialize Pydantic model to compact JSON bytes."""
    return payload.model_dump_json(by_alias=True, exclude_none=False).encode("utf-8")


def generate_webhook_signature(payload: bytes, secret: str, timestamp: str) -> str:
    """Generate HMAC-SHA256 signature for webhook payload."""
    signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
    hmac_obj = hmac.new(
        secret.encode("utf-8"),
        signed_payload.encode("utf-8"),
        hashlib.sha256,
    )
    return hmac_obj.hexdigest()


def build_webhook_headers(
    event_type: str,
    payload_bytes: bytes,
    webhook_secret: str | None = None,
    retry_count: int = 0,
) -> dict[str, str]:
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "Reflector-Webhook/1.0",
        "X-Webhook-Event": event_type,
        "X-Webhook-Retry": str(retry_count),
    }

    if webhook_secret:
        timestamp = str(int(datetime.now(timezone.utc).timestamp()))
        signature = generate_webhook_signature(payload_bytes, webhook_secret, timestamp)
        headers["X-Webhook-Signature"] = f"t={timestamp},v1={signature}"

    return headers


async def send_webhook_request(
    url: str,
    payload: BaseModel,
    event_type: str,
    webhook_secret: str | None = None,
    retry_count: int = 0,
    timeout: float = 30.0,
) -> httpx.Response:
    """Send webhook request with proper headers and signature.

    Raises:
        httpx.HTTPStatusError: On non-2xx response
        httpx.ConnectError: On connection failure
        httpx.TimeoutException: On timeout
    """
    payload_bytes = _serialize_payload(payload)

    headers = build_webhook_headers(
        event_type=event_type,
        payload_bytes=payload_bytes,
        webhook_secret=webhook_secret,
        retry_count=retry_count,
    )

    async with httpx.AsyncClient(timeout=timeout) as client:
        response = await client.post(url, content=payload_bytes, headers=headers)
        response.raise_for_status()
        return response


async def fetch_transcript_webhook_payload(
    transcript_id: NonEmptyString,
    room_id: NonEmptyString,
) -> Union[WebhookPayload, str]:
    """Build webhook payload by fetching transcript and room data from database."""

    transcript = await transcripts_controller.get_by_id(transcript_id)
    if not transcript:
        return f"Transcript {transcript_id} not found"

    room = await rooms_controller.get_by_id(room_id)
    if not room:
        return f"Room {room_id} not found"

    topics_data = [
        WebhookTopicPayload(
            title=topic.title,
            summary=topic.summary,
            timestamp=topic.timestamp,
            duration=topic.duration,
            webvtt=topics_to_webvtt([topic]) if topic.words else "",
        )
        for topic in (transcript.topics or [])
    ]

    participants_data = [
        WebhookParticipantPayload(id=p.id, name=p.name, speaker=p.speaker)
        for p in (transcript.participants or [])
    ]

    calendar_event_data: WebhookCalendarEventPayload | None = None
    try:
        if transcript.meeting_id:
            meeting = await meetings_controller.get_by_id(transcript.meeting_id)
            if meeting and meeting.calendar_event_id:
                calendar_event = await calendar_events_controller.get_by_id(
                    meeting.calendar_event_id
                )
                if calendar_event:
                    calendar_event_data = WebhookCalendarEventPayload(
                        id=calendar_event.id,
                        ics_uid=calendar_event.ics_uid,
                        title=calendar_event.title,
                        start_time=calendar_event.start_time,
                        end_time=calendar_event.end_time,
                        description=calendar_event.description or None,
                        location=calendar_event.location or None,
                        attendees=calendar_event.attendees or None,
                    )
    except Exception as e:
        logger.warning(
            "Failed to fetch calendar event for webhook",
            transcript_id=transcript_id,
            meeting_id=transcript.meeting_id,
            error=str(e),
        )

    frontend_url = f"{settings.UI_BASE_URL}/transcripts/{transcript.id}"

    return WebhookPayload(
        event="transcript.completed",
        event_id=uuid.uuid4().hex,
        timestamp=datetime.now(timezone.utc),
        transcript=WebhookTranscriptPayload(
            id=transcript.id,
            room_id=transcript.room_id,
            created_at=transcript.created_at,
            duration=transcript.duration,
            title=transcript.title,
            short_summary=transcript.short_summary,
            long_summary=transcript.long_summary,
            webvtt=transcript.webvtt,
            topics=topics_data,
            participants=participants_data,
            source_language=transcript.source_language,
            target_language=transcript.target_language,
            status=transcript.status,
            frontend_url=frontend_url,
            action_items=transcript.action_items,
        ),
        room=WebhookRoomPayload(
            id=room.id,
            name=room.name,
        ),
        calendar_event=calendar_event_data,
    )


async def fetch_test_webhook_payload(
    room_id: NonEmptyString,
) -> WebhookTestPayload | None:
    """Build test webhook payload."""

    room = await rooms_controller.get_by_id(room_id)
    if not room:
        return None

    return WebhookTestPayload(
        event="test",
        event_id=uuid.uuid4().hex,
        timestamp=datetime.now(timezone.utc),
        message="This is a test webhook from Reflector",
        room=WebhookRoomPayload(
            id=room.id,
            name=room.name,
        ),
    )
