"""Tests for WebhookConverter infrastructure conversions."""

from __future__ import annotations

from datetime import UTC, datetime
from typing import cast
from unittest.mock import MagicMock
from uuid import uuid4

import pytest

from noteflow.domain.webhooks import WebhookConfig, WebhookDelivery, WebhookEventType
from noteflow.infrastructure.converters.webhook_converters import WebhookConverter


class TestWebhookConverterConfigToDomain:
    """Tests for WebhookConverter.config_to_domain."""

    @pytest.fixture
    def mock_webhook_config_model(self) -> MagicMock:
        """Create mock ORM WebhookConfigModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.workspace_id = uuid4()
        model.name = "Test Webhook"
        model.url = "https://example.com/webhook"
        model.events = ["meeting.completed", "summary.generated"]
        model.secret = "test-secret-key"
        model.enabled = True
        model.timeout_ms = 10000
        model.max_retries = 3
        model.created_at = datetime(2024, 1, 15, 10, 0, 0, tzinfo=UTC)
        model.updated_at = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
        return model

    def test_config_orm_to_domain(
        self, mock_webhook_config_model: MagicMock
    ) -> None:
        """Convert ORM model to domain WebhookConfig."""
        result = WebhookConverter.config_to_domain(mock_webhook_config_model)

        assert isinstance(result, WebhookConfig), "Should return WebhookConfig instance"
        assert result.id == mock_webhook_config_model.id, "ID should match"
        assert result.workspace_id == mock_webhook_config_model.workspace_id, "Workspace ID should match"
        assert result.name == "Test Webhook", "Name should match"
        assert result.url == "https://example.com/webhook", "URL should match"
        assert result.secret == "test-secret-key", "Secret should match"
        assert result.enabled is True, "Enabled should match"
        assert result.timeout_ms == mock_webhook_config_model.timeout_ms, "Timeout should match"
        assert result.max_retries == mock_webhook_config_model.max_retries, "Max retries should match"

    def test_converts_event_strings_to_enum_frozenset(
        self, mock_webhook_config_model: MagicMock
    ) -> None:
        """Event type strings are converted to WebhookEventType frozenset."""
        result = WebhookConverter.config_to_domain(mock_webhook_config_model)

        assert isinstance(result.events, frozenset), "Events should be frozenset"
        assert WebhookEventType.MEETING_COMPLETED in result.events, "Should contain MEETING_COMPLETED event"
        assert WebhookEventType.SUMMARY_GENERATED in result.events, "Should contain SUMMARY_GENERATED event"
        assert len(result.events) == 2, "Should have exactly 2 events"

    @pytest.mark.parametrize(
        ("event_string", "expected_enum"),
        [
            ("meeting.completed", WebhookEventType.MEETING_COMPLETED),
            ("summary.generated", WebhookEventType.SUMMARY_GENERATED),
            ("recording.started", WebhookEventType.RECORDING_STARTED),
            ("recording.stopped", WebhookEventType.RECORDING_STOPPED),
        ],
    )
    def test_all_event_types_convert_correctly(
        self,
        mock_webhook_config_model: MagicMock,
        event_string: str,
        expected_enum: WebhookEventType,
    ) -> None:
        """All event type strings convert to correct enum values."""
        mock_webhook_config_model.events = [event_string]
        result = WebhookConverter.config_to_domain(mock_webhook_config_model)
        assert expected_enum in result.events, f"Should contain {expected_enum.value} event"

    def test_handles_empty_events_list(
        self, mock_webhook_config_model: MagicMock
    ) -> None:
        """Empty events list results in empty frozenset."""
        mock_webhook_config_model.events = []
        result = WebhookConverter.config_to_domain(mock_webhook_config_model)
        assert result.events == frozenset(), "Empty events list should convert to empty frozenset"

    def test_handles_none_secret(self, mock_webhook_config_model: MagicMock) -> None:
        """None secret in ORM is preserved as None in domain."""
        mock_webhook_config_model.secret = None
        result = WebhookConverter.config_to_domain(mock_webhook_config_model)
        assert result.secret is None, "None secret should be preserved"


class TestWebhookConverterConfigToOrmKwargs:
    """Tests for WebhookConverter.config_to_orm_kwargs."""

    @pytest.fixture
    def webhook_config_entity(self) -> WebhookConfig:
        """Create domain WebhookConfig entity for testing."""
        return WebhookConfig(
            id=uuid4(),
            workspace_id=uuid4(),
            url="https://example.com/webhook",
            events=frozenset(
                [WebhookEventType.MEETING_COMPLETED, WebhookEventType.RECORDING_STARTED]
            ),
            name="Test Webhook",
            secret="secret-key-123",
            enabled=True,
            timeout_ms=15000,
            max_retries=5,
            created_at=datetime(2024, 1, 15, 10, 0, 0, tzinfo=UTC),
            updated_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )

    def test_config_to_orm_kwargs(
        self, webhook_config_entity: WebhookConfig
    ) -> None:
        """Convert domain WebhookConfig to ORM kwargs dict."""
        result = WebhookConverter.config_to_orm_kwargs(webhook_config_entity)

        assert result["id"] == webhook_config_entity.id, "ID should be preserved"
        assert result["workspace_id"] == webhook_config_entity.workspace_id, "Workspace ID should be preserved"
        assert result["name"] == "Test Webhook", "Name should be preserved"
        assert result["url"] == "https://example.com/webhook", "URL should be preserved"
        assert result["secret"] == "secret-key-123", "Secret should be preserved"
        assert result["enabled"] is True, "Enabled should be preserved"
        assert result["timeout_ms"] == webhook_config_entity.timeout_ms, "Timeout should be preserved"
        assert result["max_retries"] == webhook_config_entity.max_retries, "Max retries should be preserved"

    def test_converts_events_frozenset_to_string_list(
        self, webhook_config_entity: WebhookConfig
    ) -> None:
        """Events frozenset is converted to list of string values."""
        result = WebhookConverter.config_to_orm_kwargs(webhook_config_entity)

        assert isinstance(result["events"], list), "Events should be list"
        # cast safe: config_to_orm_kwargs returns list[str] for "events" key
        events_list = cast(list[str], result["events"])
        assert set(events_list) == {"meeting.completed", "recording.started"}, "Events should convert to correct strings"

    @pytest.mark.parametrize(
        ("event_enum", "expected_string"),
        [
            (WebhookEventType.MEETING_COMPLETED, "meeting.completed"),
            (WebhookEventType.SUMMARY_GENERATED, "summary.generated"),
            (WebhookEventType.RECORDING_STARTED, "recording.started"),
            (WebhookEventType.RECORDING_STOPPED, "recording.stopped"),
        ],
    )
    def test_all_event_enums_convert_to_strings(
        self, event_enum: WebhookEventType, expected_string: str
    ) -> None:
        """All WebhookEventType enums convert to correct string values."""
        config = WebhookConfig(
            id=uuid4(),
            workspace_id=uuid4(),
            url="https://example.com/webhook",
            events=frozenset([event_enum]),
        )
        result = WebhookConverter.config_to_orm_kwargs(config)
        # cast safe: config_to_orm_kwargs returns list[str] for "events" key
        events_list = cast(list[str], result["events"])
        assert isinstance(events_list, list), "Events should be a list"
        assert expected_string in events_list, f"Should contain {expected_string} string"

    def test_handles_empty_events_frozenset(self) -> None:
        """Empty events frozenset results in empty list."""
        config = WebhookConfig(
            id=uuid4(),
            workspace_id=uuid4(),
            url="https://example.com/webhook",
            events=frozenset(),
        )
        result = WebhookConverter.config_to_orm_kwargs(config)
        assert result["events"] == [], "Empty frozenset should convert to empty list"


class TestWebhookConverterDeliveryToDomain:
    """Tests for WebhookConverter.delivery_to_domain."""

    @pytest.fixture
    def mock_webhook_delivery_model(self) -> MagicMock:
        """Create mock ORM WebhookDeliveryModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.webhook_id = uuid4()
        model.event_type = "meeting.completed"
        model.payload = {"event": "meeting.completed", "meeting_id": str(uuid4())}
        model.status_code = 200
        model.response_body = '{"status": "ok"}'
        model.error_message = None
        model.attempt_count = 1
        model.duration_ms = 150
        model.delivered_at = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
        return model

    def test_delivery_orm_to_domain(
        self, mock_webhook_delivery_model: MagicMock
    ) -> None:
        """Convert ORM model to domain WebhookDelivery."""
        result = WebhookConverter.delivery_to_domain(mock_webhook_delivery_model)

        assert isinstance(result, WebhookDelivery), "Should return WebhookDelivery"
        assert result.id == mock_webhook_delivery_model.id, "ID should match"
        assert result.webhook_id == mock_webhook_delivery_model.webhook_id, "Webhook ID should match"
        assert result.event_type == WebhookEventType.MEETING_COMPLETED, "Event type should be MEETING_COMPLETED"
        assert result.status_code == mock_webhook_delivery_model.status_code, "Status code should match"
        assert result.response_body == mock_webhook_delivery_model.response_body, "Response body should match"
        assert result.error_message is None, "Error message should be None"
        assert result.attempt_count == mock_webhook_delivery_model.attempt_count, "Attempt count should match"
        assert result.duration_ms == mock_webhook_delivery_model.duration_ms, "Duration should match"

    def test_converts_event_type_string_to_enum(
        self, mock_webhook_delivery_model: MagicMock
    ) -> None:
        """Event type string is converted to WebhookEventType enum."""
        mock_webhook_delivery_model.event_type = "summary.generated"
        result = WebhookConverter.delivery_to_domain(mock_webhook_delivery_model)
        assert result.event_type == WebhookEventType.SUMMARY_GENERATED, "Event type should be SUMMARY_GENERATED"
        assert isinstance(result.event_type, WebhookEventType), "Event type should be WebhookEventType enum"

    def test_converts_payload_dict(
        self, mock_webhook_delivery_model: MagicMock
    ) -> None:
        """Payload is converted to dict from ORM model."""
        result = WebhookConverter.delivery_to_domain(mock_webhook_delivery_model)
        assert isinstance(result.payload, dict), "Payload should be dict"
        assert result.payload["event"] == "meeting.completed", "Payload event field should match"

    def test_handles_failed_delivery(
        self, mock_webhook_delivery_model: MagicMock
    ) -> None:
        """Failed delivery with error message is converted correctly."""
        mock_webhook_delivery_model.status_code = None
        mock_webhook_delivery_model.error_message = "Connection timeout"
        mock_webhook_delivery_model.duration_ms = None

        result = WebhookConverter.delivery_to_domain(mock_webhook_delivery_model)

        assert result.status_code is None, "Status code should be None"
        assert result.error_message == "Connection timeout", "Error message should match"
        assert result.duration_ms is None, "Duration should be None"


class TestWebhookConverterDeliveryToOrmKwargs:
    """Tests for WebhookConverter.delivery_to_orm_kwargs."""

    def test_delivery_to_orm_kwargs(self) -> None:
        """Convert domain WebhookDelivery to ORM kwargs dict."""
        delivery = WebhookDelivery(
            id=uuid4(),
            webhook_id=uuid4(),
            event_type=WebhookEventType.SUMMARY_GENERATED,
            payload={"event": "summary.generated", "title": "Test Meeting"},
            status_code=201,
            response_body='{"received": true}',
            error_message=None,
            attempt_count=2,
            duration_ms=200,
            delivered_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )

        result = WebhookConverter.delivery_to_orm_kwargs(delivery)

        assert result["id"] == delivery.id, "ID should be preserved"
        assert result["webhook_id"] == delivery.webhook_id, "Webhook ID should be preserved"
        assert result["event_type"] == "summary.generated", "Event type as string"
        assert result["status_code"] == delivery.status_code, "Status code should be preserved"
        assert result["attempt_count"] == delivery.attempt_count, "Attempt count should be preserved"

    def test_converts_event_type_enum_to_string(self) -> None:
        """WebhookEventType enum is converted to its string value."""
        delivery = WebhookDelivery(
            id=uuid4(),
            webhook_id=uuid4(),
            event_type=WebhookEventType.RECORDING_STOPPED,
            payload={},
            status_code=200,
            response_body=None,
            error_message=None,
            attempt_count=1,
            duration_ms=100,
            delivered_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )

        result = WebhookConverter.delivery_to_orm_kwargs(delivery)

        assert result["event_type"] == "recording.stopped", "Event type should be recording.stopped string"
        assert isinstance(result["event_type"], str), "Event type should be a string"


class TestWebhookConverterRoundTrip:
    """Tests for round-trip conversion fidelity."""

    def test_config_domain_to_orm_to_domain_preserves_values(self) -> None:
        """Round-trip conversion preserves all WebhookConfig field values."""
        original = WebhookConfig(
            id=uuid4(),
            workspace_id=uuid4(),
            url="https://example.com/webhook",
            events=frozenset(
                [WebhookEventType.MEETING_COMPLETED, WebhookEventType.SUMMARY_GENERATED]
            ),
            name="Round Trip Webhook",
            secret="round-trip-secret",
            enabled=True,
            timeout_ms=20000,
            max_retries=4,
            created_at=datetime(2024, 1, 15, 10, 0, 0, tzinfo=UTC),
            updated_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )

        orm_kwargs = WebhookConverter.config_to_orm_kwargs(original)
        mock_orm = MagicMock()
        mock_orm.id = orm_kwargs["id"]
        mock_orm.workspace_id = orm_kwargs["workspace_id"]
        mock_orm.name = orm_kwargs["name"]
        mock_orm.url = orm_kwargs["url"]
        mock_orm.events = orm_kwargs["events"]
        mock_orm.secret = orm_kwargs["secret"]
        mock_orm.enabled = orm_kwargs["enabled"]
        mock_orm.timeout_ms = orm_kwargs["timeout_ms"]
        mock_orm.max_retries = orm_kwargs["max_retries"]
        mock_orm.created_at = orm_kwargs["created_at"]
        mock_orm.updated_at = orm_kwargs["updated_at"]

        result = WebhookConverter.config_to_domain(mock_orm)

        assert result.id == original.id, "ID preserved through round-trip"
        assert result.workspace_id == original.workspace_id, "Workspace ID preserved through round-trip"
        assert result.name == original.name, "Name preserved"
        assert result.url == original.url, "URL preserved"
        assert result.events == original.events, "Events preserved"
        assert result.secret == original.secret, "Secret preserved"
        assert result.enabled == original.enabled, "Enabled preserved"
        assert result.timeout_ms == original.timeout_ms, "Timeout preserved"
        assert result.max_retries == original.max_retries, "Max retries preserved"

    def test_delivery_domain_to_orm_to_domain_preserves_values(self) -> None:
        """Round-trip conversion preserves all WebhookDelivery field values."""
        original = WebhookDelivery(
            id=uuid4(),
            webhook_id=uuid4(),
            event_type=WebhookEventType.RECORDING_STARTED,
            payload={"event": "recording.started", "title": "Test"},
            status_code=200,
            response_body='{"ok": true}',
            error_message=None,
            attempt_count=1,
            duration_ms=150,
            delivered_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )

        orm_kwargs = WebhookConverter.delivery_to_orm_kwargs(original)
        mock_orm = MagicMock()
        mock_orm.id = orm_kwargs["id"]
        mock_orm.webhook_id = orm_kwargs["webhook_id"]
        mock_orm.event_type = orm_kwargs["event_type"]
        mock_orm.payload = orm_kwargs["payload"]
        mock_orm.status_code = orm_kwargs["status_code"]
        mock_orm.response_body = orm_kwargs["response_body"]
        mock_orm.error_message = orm_kwargs["error_message"]
        mock_orm.attempt_count = orm_kwargs["attempt_count"]
        mock_orm.duration_ms = orm_kwargs["duration_ms"]
        mock_orm.delivered_at = orm_kwargs["delivered_at"]

        result = WebhookConverter.delivery_to_domain(mock_orm)

        assert result.id == original.id, "ID preserved"
        assert result.webhook_id == original.webhook_id, "Webhook ID preserved through round-trip"
        assert result.event_type == original.event_type, "Event type preserved"
        assert result.payload == original.payload, "Payload preserved"
        assert result.status_code == original.status_code, "Status code preserved"
        assert result.attempt_count == original.attempt_count, "Attempt count preserved"
