"""Integration tests for webhook triggering in gRPC service.

Tests the complete webhook flow from gRPC operations to webhook delivery.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, TypedDict
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4

import grpc
import pytest

from noteflow.application.services.webhook_service import WebhookService
from noteflow.domain.entities import Meeting, Segment
from noteflow.domain.webhooks import (
    DeliveryResult,
    WebhookConfig,
    WebhookDelivery,
    WebhookEventType,
)
from noteflow.grpc._config import ServicesConfig
from noteflow.grpc.proto import noteflow_pb2
from noteflow.grpc.service import NoteFlowServicer
from noteflow.infrastructure.persistence.unit_of_work import SqlAlchemyUnitOfWork
from noteflow.infrastructure.webhooks import WebhookExecutor

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker


class CapturedWebhookCall(TypedDict):
    """Structure for captured webhook call data."""

    config_url: str
    event_type: WebhookEventType
    payload: dict[str, str]


class MockGrpcContext:
    """Mock gRPC context for testing."""

    def __init__(self) -> None:
        """Initialize mock context."""
        self.aborted = False
        self.abort_code: grpc.StatusCode | None = None
        self.abort_details: str | None = None

    async def abort(self, code: grpc.StatusCode, details: str) -> None:
        """Record abort and raise to simulate gRPC behavior."""
        self.aborted = True
        self.abort_code = code
        self.abort_details = details
        raise grpc.RpcError()


@pytest.fixture
def captured_webhook_calls() -> list[CapturedWebhookCall]:
    """Store webhook calls for verification."""
    return []


@pytest.fixture
def mock_webhook_executor(captured_webhook_calls: list[CapturedWebhookCall]) -> MagicMock:
    """Create a mock executor that captures calls."""
    executor = MagicMock(spec=WebhookExecutor)

    async def capture_delivery(
        config: WebhookConfig,
        event_type: WebhookEventType,
        payload: dict[str, str],
    ) -> WebhookDelivery:
        captured_webhook_calls.append({
            "config_url": config.url,
            "event_type": event_type,
            "payload": payload,
        })
        result = DeliveryResult(status_code=200)
        return WebhookDelivery.create(
            webhook_id=config.id,
            event_type=event_type,
            payload=payload,
            result=result,
        )

    executor.deliver = AsyncMock(side_effect=capture_delivery)
    executor.close = AsyncMock()
    return executor


@pytest.fixture
def webhook_service_with_config(
    mock_webhook_executor: MagicMock,
) -> WebhookService:
    """Create a webhook service with a registered webhook."""
    service = WebhookService(executor=mock_webhook_executor)
    config = WebhookConfig.create(
        workspace_id=uuid4(),
        url="https://test.example.com/webhook",
        events=[
            WebhookEventType.MEETING_COMPLETED,
            WebhookEventType.RECORDING_STARTED,
            WebhookEventType.RECORDING_STOPPED,
        ],
        name="Integration Test Webhook",
    )
    service.register_webhook(config)
    return service


@pytest.mark.integration
class TestStopMeetingTriggersWebhook:
    """Test that StopMeeting triggers webhook delivery."""

    async def test_stop_meeting_triggers_meeting_completed_webhook(
        self,
        session_factory: async_sessionmaker[AsyncSession],
        meetings_dir: Path,
        webhook_service_with_config: WebhookService,
        captured_webhook_calls: list[CapturedWebhookCall],
    ) -> None:
        """Stopping a meeting triggers meeting.completed webhook."""
        async with SqlAlchemyUnitOfWork(session_factory, meetings_dir) as uow:
            meeting = Meeting.create(title="Webhook Integration Test")
            meeting.start_recording()
            await uow.meetings.create(meeting)
            await uow.segments.add(meeting.id, Segment(0, "Test segment", 0.0, 5.0, meeting_id=meeting.id))
            await uow.commit()
            meeting_id = str(meeting.id)

        servicer = NoteFlowServicer(session_factory=session_factory, services=ServicesConfig(webhook_service=webhook_service_with_config))
        result = await servicer.StopMeeting(noteflow_pb2.StopMeetingRequest(meeting_id=meeting_id), MockGrpcContext())

        assert result.state == noteflow_pb2.MEETING_STATE_STOPPED, f"got {result.state}"
        assert len(captured_webhook_calls) == 2, f"got {len(captured_webhook_calls)}"

        event_types = {c["event_type"] for c in captured_webhook_calls}
        assert WebhookEventType.RECORDING_STOPPED in event_types, f"not in {event_types}"
        assert WebhookEventType.MEETING_COMPLETED in event_types, f"not in {event_types}"

        completed_calls: list[CapturedWebhookCall] = [
            c for c in captured_webhook_calls
            if c["event_type"] == WebhookEventType.MEETING_COMPLETED
        ]
        assert len(completed_calls) == 1, "should have one MEETING_COMPLETED"
        assert completed_calls[0]["payload"]["meeting_id"] == meeting_id, "meeting_id mismatch"
        assert completed_calls[0]["payload"]["title"] == "Webhook Integration Test", "title mismatch"

    async def test_stop_meeting_with_failed_webhook_still_succeeds(
        self,
        session_factory: async_sessionmaker[AsyncSession],
        meetings_dir: Path,
        mock_webhook_executor: MagicMock,
    ) -> None:
        """Meeting stop succeeds even when webhook delivery fails."""
        mock_webhook_executor.deliver = AsyncMock(
            side_effect=RuntimeError("Webhook server unreachable")
        )

        webhook_service = WebhookService(executor=mock_webhook_executor)
        webhook_service.register_webhook(
            WebhookConfig.create(
                workspace_id=uuid4(),
                url="https://failing.example.com/webhook",
                events=[WebhookEventType.MEETING_COMPLETED],
            )
        )

        async with SqlAlchemyUnitOfWork(session_factory, meetings_dir) as uow:
            meeting = Meeting.create(title="Webhook Failure Test")
            meeting.start_recording()
            await uow.meetings.create(meeting)
            await uow.commit()
            meeting_id = str(meeting.id)

        servicer = NoteFlowServicer(
            session_factory=session_factory,
            services=ServicesConfig(webhook_service=webhook_service),
        )

        request = noteflow_pb2.StopMeetingRequest(meeting_id=meeting_id)
        result = await servicer.StopMeeting(request, MockGrpcContext())

        # Meeting stop succeeds despite webhook failure
        assert result.state == noteflow_pb2.MEETING_STATE_STOPPED, (
            f"expected meeting state STOPPED despite webhook failure, got {result.state}"
        )


@pytest.mark.integration
class TestNoWebhookServiceGracefulDegradation:
    """Test that operations work without webhook service configured."""

    async def test_stop_meeting_works_withoutwebhook_service(
        self,
        session_factory: async_sessionmaker[AsyncSession],
        meetings_dir: Path,
    ) -> None:
        """Meeting operations work when no webhook service is configured."""
        async with SqlAlchemyUnitOfWork(session_factory, meetings_dir) as uow:
            meeting = Meeting.create(title="No Webhooks Test")
            meeting.start_recording()
            await uow.meetings.create(meeting)
            await uow.commit()
            meeting_id = str(meeting.id)

        servicer = NoteFlowServicer(
            session_factory=session_factory,
            services=ServicesConfig(webhook_service=None),
        )

        request = noteflow_pb2.StopMeetingRequest(meeting_id=meeting_id)
        result = await servicer.StopMeeting(request, MockGrpcContext())

        assert result.state == noteflow_pb2.MEETING_STATE_STOPPED, (
            f"expected meeting state STOPPED without webhook service, got {result.state}"
        )
