import json
from typing import assert_never

from fastapi import APIRouter, HTTPException, Request
from pydantic import TypeAdapter

from reflector.dailyco_api import (
    DailyWebhookEventUnion,
    ParticipantJoinedEvent,
    ParticipantLeftEvent,
    RecordingErrorEvent,
    RecordingReadyEvent,
    RecordingStartedEvent,
)
from reflector.db.meetings import meetings_controller
from reflector.logger import logger as _logger
from reflector.settings import settings
from reflector.storage import get_source_storage
from reflector.video_platforms.factory import create_platform_client
from reflector.worker.process import (
    poll_daily_room_presence_task,
    process_multitrack_recording,
    store_cloud_recording,
)

router = APIRouter()

logger = _logger.bind(platform="daily")


@router.post("/webhook")
async def webhook(request: Request):
    """Handle Daily webhook events.

    Example webhook payload:
    {
      "version": "1.0.0",
      "type": "recording.ready-to-download",
      "id": "rec-rtd-c3df927c-f738-4471-a2b7-066fa7e95a6b-1692124192",
      "payload": {
        "recording_id": "08fa0b24-9220-44c5-846c-3f116cf8e738",
        "room_name": "Xcm97xRZ08b2dePKb78g",
        "start_ts": 1692124183,
        "status": "finished",
        "max_participants": 1,
        "duration": 9,
        "share_token": "ntDCL5k98Ulq", #gitleaks:allow
        "s3_key": "api-test-1j8fizhzd30c/Xcm97xRZ08b2dePKb78g/1692124183028"
      },
      "event_ts": 1692124192
    }

    Daily.co circuit-breaker: After 3+ failed responses (4xx/5xx), webhook
    state→FAILED, stops sending events. Reset: scripts/recreate_daily_webhook.py
    """
    body = await request.body()
    signature = request.headers.get("X-Webhook-Signature", "")
    timestamp = request.headers.get("X-Webhook-Timestamp", "")

    client = create_platform_client("daily")

    if not client.verify_webhook_signature(body, signature, timestamp):
        logger.warning(
            "Invalid webhook signature",
            signature=signature,
            timestamp=timestamp,
            has_body=bool(body),
        )
        raise HTTPException(status_code=401, detail="Invalid webhook signature")

    try:
        body_json = json.loads(body)
    except json.JSONDecodeError:
        raise HTTPException(status_code=422, detail="Invalid JSON")

    if body_json.get("test") == "test":
        logger.info("Received Daily webhook test event")
        return {"status": "ok"}

    event_adapter = TypeAdapter(DailyWebhookEventUnion)
    try:
        event = event_adapter.validate_python(body_json)
    except Exception as e:
        err_detail = str(e)
        if hasattr(e, "errors"):
            err_detail = f"{err_detail}; errors={e.errors()!r}"
        logger.error(
            "Failed to parse webhook event",
            error=err_detail,
            body=body.decode(),
        )
        raise HTTPException(status_code=422, detail="Invalid event format")

    match event:
        case ParticipantJoinedEvent():
            await _handle_participant_joined(event)
        case ParticipantLeftEvent():
            await _handle_participant_left(event)
        case RecordingStartedEvent():
            await _handle_recording_started(event)
        case RecordingReadyEvent():
            await _handle_recording_ready(event)
        case RecordingErrorEvent():
            await _handle_recording_error(event)
        case _:
            assert_never(event)

    return {"status": "ok"}


async def _queue_poll_for_room(
    room_name: str | None,
    event_type: str,
    user_id: str | None,
    session_id: str | None,
    **log_kwargs,
) -> None:
    """Queue poll task for room by name, handling missing room/meeting cases."""
    if not room_name:
        logger.warning(f"{event_type}: no room in payload")
        return

    meeting = await meetings_controller.get_by_room_name(room_name)
    if not meeting:
        logger.warning(f"{event_type}: meeting not found", room_name=room_name)
        return

    poll_daily_room_presence_task.delay(meeting.id)

    logger.info(
        f"{event_type.replace('.', ' ').title()} - poll queued",
        meeting_id=meeting.id,
        room_name=room_name,
        user_id=user_id,
        session_id=session_id,
        **log_kwargs,
    )


async def _handle_participant_joined(event: ParticipantJoinedEvent):
    """Queue poll task for presence reconciliation."""
    await _queue_poll_for_room(
        event.payload.room_name,
        "participant.joined",
        event.payload.user_id,
        event.payload.session_id,
        user_name=event.payload.user_name,
    )


async def _handle_participant_left(event: ParticipantLeftEvent):
    """Queue poll task for presence reconciliation."""
    await _queue_poll_for_room(
        event.payload.room_name,
        "participant.left",
        event.payload.user_id,
        event.payload.session_id,
        duration=event.payload.duration,
    )


async def _handle_recording_started(event: RecordingStartedEvent):
    room_name = event.payload.room_name
    if not room_name:
        logger.warning(
            "recording.started: no room_name in payload", payload=event.payload
        )
        return

    meeting = await meetings_controller.get_by_room_name(room_name)
    if meeting:
        logger.info(
            "Recording started",
            meeting_id=meeting.id,
            room_name=room_name,
            recording_id=event.payload.recording_id,
            platform="daily",
        )
    else:
        logger.warning("recording.started: meeting not found", room_name=room_name)


async def _handle_recording_ready(event: RecordingReadyEvent):
    room_name = event.payload.room_name
    recording_id = event.payload.recording_id
    recording_type = event.payload.type

    logger.info(
        "Recording ready for download",
        room_name=room_name,
        recording_id=recording_id,
        recording_type=recording_type,
        platform="daily",
    )

    bucket_name = settings.DAILYCO_STORAGE_AWS_BUCKET_NAME
    if not bucket_name:
        logger.error("DAILYCO_STORAGE_AWS_BUCKET_NAME not configured")
        return

    if recording_type == "cloud":
        await store_cloud_recording(
            recording_id=recording_id,
            room_name=room_name,
            s3_key=event.payload.s3_key,
            duration=event.payload.duration,
            start_ts=event.payload.start_ts,
            source="webhook",
        )

    elif recording_type == "raw-tracks":
        tracks = event.payload.tracks
        if not tracks:
            logger.warning(
                "raw-tracks recording: missing tracks array",
                room_name=room_name,
                recording_id=recording_id,
            )
            return

        track_keys = [t.s3Key for t in tracks if t.type == "audio"]

        # Delete video tracks when store_video is disabled (same pattern as LiveKit).
        # Only delete if we have a meeting AND store_video is explicitly false.
        # If no meeting found, leave files alone (can't confirm user intent).
        video_track_keys = [t.s3Key for t in tracks if t.type == "video"]
        if video_track_keys:
            meeting = await meetings_controller.get_by_room_name(room_name)
            if meeting is not None and not meeting.store_video:
                storage = get_source_storage("daily")
                for video_key in video_track_keys:
                    try:
                        await storage.delete_file(video_key)
                        logger.info(
                            "Deleted video track from raw-tracks recording",
                            s3_key=video_key,
                            room_name=room_name,
                        )
                    except Exception as e:
                        # Non-critical — pipeline filters these out anyway
                        logger.warning(
                            "Failed to delete video track from raw-tracks recording",
                            s3_key=video_key,
                            error=str(e),
                        )

        logger.info(
            "Raw-tracks recording queuing processing",
            recording_id=recording_id,
            room_name=room_name,
            num_tracks=len(track_keys),
        )

        process_multitrack_recording.delay(
            bucket_name=bucket_name,
            daily_room_name=room_name,
            recording_id=recording_id,
            track_keys=track_keys,
            recording_start_ts=event.payload.start_ts,
        )

    else:
        logger.warning(
            "Unknown recording type",
            recording_type=recording_type,
            recording_id=recording_id,
        )


async def _handle_recording_error(event: RecordingErrorEvent):
    payload = event.payload
    room_name = payload.room_name

    meeting = await meetings_controller.get_by_room_name(room_name)
    if meeting:
        logger.error(
            "Recording error",
            meeting_id=meeting.id,
            room_name=room_name,
            error=payload.error_msg,
            platform="daily",
        )
    else:
        logger.warning("recording.error: meeting not found", room_name=room_name)
