"""Tests for CalendarEventConverter infrastructure conversions."""

from __future__ import annotations

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

import pytest

from noteflow.domain.ports.calendar import CalendarEventInfo
from noteflow.infrastructure.converters.calendar_converters import CalendarEventConverter
from noteflow.infrastructure.triggers.calendar import CalendarEvent


class TestCalendarEventConverterOrmToInfo:
    """Tests for CalendarEventConverter.orm_to_info."""

    @pytest.fixture
    def mock_calendar_event_model(self) -> MagicMock:
        """Create mock ORM CalendarEventModel with typical values."""
        model = MagicMock()
        model.external_id = "event-123"
        model.title = "Team Standup"
        model.start_time = datetime(2024, 1, 15, 9, 0, 0, tzinfo=UTC)
        model.end_time = datetime(2024, 1, 15, 9, 30, 0, tzinfo=UTC)
        model.attendees = ["alice@example.com", "bob@example.com"]
        model.location = "Conference Room A"
        model.description = "Daily standup meeting"
        model.meeting_link = "https://meet.google.com/abc-defg-hij"
        model.is_all_day = False
        model.raw = {"organizer": "alice@example.com"}
        return model

    def test_converts_orm_to_domain_event_info(
        self, mock_calendar_event_model: MagicMock
    ) -> None:
        """Convert ORM model to domain CalendarEventInfo."""
        result = CalendarEventConverter.orm_to_info(
            mock_calendar_event_model, provider="google"
        )

        assert isinstance(result, CalendarEventInfo), "Should return CalendarEventInfo"
        assert result.id == "event-123", "ID should match external_id"
        assert result.title == "Team Standup", "Title should match"
        assert result.start_time == mock_calendar_event_model.start_time, "Start time should match"
        assert result.end_time == mock_calendar_event_model.end_time, "End time should match"
        assert result.location == "Conference Room A", "Location should match"
        assert result.description == "Daily standup meeting", "Description should match"
        assert result.meeting_url == "https://meet.google.com/abc-defg-hij", "Meeting URL should match"
        assert result.is_all_day is False, "is_all_day should match"
        assert result.provider == "google", "Provider should match argument"

    def test_converts_attendees_to_tuple(
        self, mock_calendar_event_model: MagicMock
    ) -> None:
        """Attendees list is converted to tuple."""
        result = CalendarEventConverter.orm_to_info(mock_calendar_event_model)

        assert isinstance(result.attendees, tuple), "Attendees should be tuple"
        assert result.attendees == (
            "alice@example.com",
            "bob@example.com",
        ), "Attendees should match expected email addresses"

    def test_handles_none_attendees(
        self, mock_calendar_event_model: MagicMock
    ) -> None:
        """None attendees in ORM becomes empty tuple in domain."""
        mock_calendar_event_model.attendees = None
        result = CalendarEventConverter.orm_to_info(mock_calendar_event_model)
        assert result.attendees == (), "None attendees should become empty tuple"

    def test_handles_empty_attendees(
        self, mock_calendar_event_model: MagicMock
    ) -> None:
        """Empty attendees list becomes empty tuple."""
        mock_calendar_event_model.attendees = []
        result = CalendarEventConverter.orm_to_info(mock_calendar_event_model)
        assert result.attendees == (), "Empty attendees list should become empty tuple"

    def test_converts_raw_dict(self, mock_calendar_event_model: MagicMock) -> None:
        """Raw JSON is converted to dict from ORM model."""
        result = CalendarEventConverter.orm_to_info(mock_calendar_event_model)
        assert isinstance(result.raw, dict), "Raw should be dict"
        assert result.raw["organizer"] == "alice@example.com", "Raw dict organizer should match"

    def test_handles_none_raw(self, mock_calendar_event_model: MagicMock) -> None:
        """None raw in ORM becomes None in domain."""
        mock_calendar_event_model.raw = None
        result = CalendarEventConverter.orm_to_info(mock_calendar_event_model)
        assert result.raw is None, "None raw in ORM should become None in domain"

    def test_default_provider_is_empty_string(
        self, mock_calendar_event_model: MagicMock
    ) -> None:
        """Provider defaults to empty string if not specified."""
        result = CalendarEventConverter.orm_to_info(mock_calendar_event_model)
        assert result.provider == "", "Provider should default to empty string"


class TestCalendarEventConverterInfoToOrmKwargs:
    """Tests for CalendarEventConverter.info_to_orm_kwargs."""

    @pytest.fixture
    def calendar_event_info(self) -> CalendarEventInfo:
        """Create domain CalendarEventInfo for testing."""
        return CalendarEventInfo(
            id="outlook-event-456",
            title="Project Review",
            start_time=datetime(2024, 1, 16, 14, 0, 0, tzinfo=UTC),
            end_time=datetime(2024, 1, 16, 15, 0, 0, tzinfo=UTC),
            attendees=("carol@example.com", "dave@example.com"),
            location="Virtual",
            description="Quarterly project review",
            meeting_url="https://teams.microsoft.com/l/meetup/xyz",
            is_recurring=True,
            is_all_day=False,
            provider="outlook",
            raw={"responseStatus": "accepted"},
        )

    def test_calendar_event_info_to_orm_kwargs(
        self, calendar_event_info: CalendarEventInfo
    ) -> None:
        """Convert domain CalendarEventInfo to ORM kwargs dict."""
        integration_id = uuid4()

        result = CalendarEventConverter.info_to_orm_kwargs(
            calendar_event_info, integration_id=integration_id
        )

        assert result["integration_id"] == integration_id, "Integration ID should match"
        assert result["external_id"] == "outlook-event-456", "ID should map to external_id"
        assert result["title"] == "Project Review", "Title should be preserved"
        assert result["start_time"] == calendar_event_info.start_time, "Start time should match"
        assert result["end_time"] == calendar_event_info.end_time, "End time should match"
        assert result["location"] == "Virtual", "Location should be preserved"
        assert result["description"] == "Quarterly project review", "Description should match"
        assert (
            result["meeting_link"] == "https://teams.microsoft.com/l/meetup/xyz"
        ), "Meeting link should match"
        assert result["is_all_day"] is False, "is_all_day should be preserved"

    def test_converts_attendees_tuple_to_list(
        self, calendar_event_info: CalendarEventInfo
    ) -> None:
        """Attendees tuple is converted to list for ORM storage."""
        integration_id = uuid4()

        result = CalendarEventConverter.info_to_orm_kwargs(
            calendar_event_info, integration_id=integration_id
        )

        assert isinstance(result["attendees"], list), "Attendees should be list"
        assert result["attendees"] == [
            "carol@example.com",
            "dave@example.com",
        ], "Attendees list should match expected email addresses"

    def test_handles_empty_attendees_tuple(self) -> None:
        """Empty attendees tuple becomes None for ORM storage."""
        event = CalendarEventInfo(
            id="event-789",
            title="Solo Work",
            start_time=datetime(2024, 1, 17, 10, 0, 0, tzinfo=UTC),
            end_time=datetime(2024, 1, 17, 11, 0, 0, tzinfo=UTC),
            attendees=(),
        )
        integration_id = uuid4()

        result = CalendarEventConverter.info_to_orm_kwargs(event, integration_id)

        assert result["attendees"] is None, "Empty attendees tuple should become None"

    def test_includes_calendar_id_and_name(
        self, calendar_event_info: CalendarEventInfo
    ) -> None:
        """Calendar ID and name are included with defaults."""
        integration_id = uuid4()

        result = CalendarEventConverter.info_to_orm_kwargs(
            calendar_event_info,
            integration_id=integration_id,
            calendar_id="work-calendar",
            calendar_name="Work Calendar",
        )

        assert result["calendar_id"] == "work-calendar", "Calendar ID should match provided value"
        assert result["calendar_name"] == "Work Calendar", "Calendar name should match provided value"

    def test_default_calendar_id_and_name(
        self, calendar_event_info: CalendarEventInfo
    ) -> None:
        """Default calendar_id and calendar_name values."""
        integration_id = uuid4()

        result = CalendarEventConverter.info_to_orm_kwargs(
            calendar_event_info, integration_id=integration_id
        )

        assert result["calendar_id"] == "primary", "Calendar ID should default to 'primary'"
        assert result["calendar_name"] == "Primary", "Calendar name should default to 'Primary'"


class TestCalendarEventConverterInfoToTriggerEvent:
    """Tests for CalendarEventConverter.info_to_trigger_event."""

    def test_converts_info_to_trigger_event(self) -> None:
        """Convert CalendarEventInfo to trigger-layer CalendarEvent."""
        info = CalendarEventInfo(
            id="event-abc",
            title="Sprint Planning",
            start_time=datetime(2024, 1, 18, 10, 0, 0, tzinfo=UTC),
            end_time=datetime(2024, 1, 18, 11, 30, 0, tzinfo=UTC),
            attendees=("team@example.com",),
            location="Room B",
            provider="google",
        )

        result = CalendarEventConverter.info_to_trigger_event(info)

        assert isinstance(result, CalendarEvent), "Should return CalendarEvent"
        assert result.start == info.start_time, "Start time should match"
        assert result.end == info.end_time, "End time should match"
        assert result.title == "Sprint Planning", "Title should match"

    def test_trigger_event_is_frozen_dataclass(self) -> None:
        """Trigger CalendarEvent is immutable (frozen dataclass)."""
        info = CalendarEventInfo(
            id="event-def",
            title="Code Review",
            start_time=datetime(2024, 1, 19, 14, 0, 0, tzinfo=UTC),
            end_time=datetime(2024, 1, 19, 15, 0, 0, tzinfo=UTC),
            attendees=(),
        )

        result = CalendarEventConverter.info_to_trigger_event(info)

        # Frozen dataclass raises FrozenInstanceError on mutation
        from dataclasses import FrozenInstanceError

        with pytest.raises(FrozenInstanceError, match="cannot assign"):
            result.title = "Changed"


class TestCalendarEventConverterOrmToTriggerEvent:
    """Tests for CalendarEventConverter.orm_to_trigger_event."""

    @pytest.fixture
    def mock_calendar_event_model(self) -> MagicMock:
        """Create mock ORM CalendarEventModel for trigger conversion."""
        model = MagicMock()
        model.title = "1:1 Meeting"
        model.start_time = datetime(2024, 1, 20, 15, 0, 0, tzinfo=UTC)
        model.end_time = datetime(2024, 1, 20, 15, 30, 0, tzinfo=UTC)
        return model

    def test_converts_orm_to_trigger_event(
        self, mock_calendar_event_model: MagicMock
    ) -> None:
        """Convert ORM model to trigger-layer CalendarEvent."""
        result = CalendarEventConverter.orm_to_trigger_event(mock_calendar_event_model)

        assert isinstance(result, CalendarEvent), "Should return CalendarEvent"
        assert result.start == mock_calendar_event_model.start_time, "Start time should match"
        assert result.end == mock_calendar_event_model.end_time, "End time should match"
        assert result.title == "1:1 Meeting", "Title should match"

    def test_direct_orm_to_trigger_matches_via_info(
        self, mock_calendar_event_model: MagicMock
    ) -> None:
        """Direct ORM->trigger conversion matches ORM->info->trigger path."""
        # Add all fields needed for orm_to_info
        mock_calendar_event_model.external_id = "event-via-info"
        mock_calendar_event_model.attendees = []
        mock_calendar_event_model.location = None
        mock_calendar_event_model.description = None
        mock_calendar_event_model.meeting_link = None
        mock_calendar_event_model.is_all_day = False
        mock_calendar_event_model.raw = None

        # Direct conversion
        direct_result = CalendarEventConverter.orm_to_trigger_event(
            mock_calendar_event_model
        )

        # Via info conversion
        info = CalendarEventConverter.orm_to_info(mock_calendar_event_model)
        via_info_result = CalendarEventConverter.info_to_trigger_event(info)

        assert direct_result.start == via_info_result.start, "Start times should match"
        assert direct_result.end == via_info_result.end, "End times should match"
        assert direct_result.title == via_info_result.title, "Titles should match"


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

    def test_info_to_orm_to_info_preserves_values(self) -> None:
        """Round-trip conversion preserves all CalendarEventInfo field values."""
        original = CalendarEventInfo(
            id="round-trip-event",
            title="Round Trip Meeting",
            start_time=datetime(2024, 1, 21, 10, 0, 0, tzinfo=UTC),
            end_time=datetime(2024, 1, 21, 11, 0, 0, tzinfo=UTC),
            attendees=("user1@example.com", "user2@example.com", "user3@example.com"),
            location="Building C, Floor 2",
            description="Testing round trip conversion",
            meeting_url="https://zoom.us/j/123456789",
            is_recurring=False,
            is_all_day=False,
            provider="google",
            raw={"recurrence": None, "status": "confirmed"},
        )

        integration_id = uuid4()
        orm_kwargs = CalendarEventConverter.info_to_orm_kwargs(
            original, integration_id=integration_id
        )

        # Simulate ORM model from kwargs
        mock_orm = MagicMock()
        mock_orm.external_id = orm_kwargs["external_id"]
        mock_orm.title = orm_kwargs["title"]
        mock_orm.start_time = orm_kwargs["start_time"]
        mock_orm.end_time = orm_kwargs["end_time"]
        mock_orm.attendees = orm_kwargs["attendees"]
        mock_orm.location = orm_kwargs["location"]
        mock_orm.description = orm_kwargs["description"]
        mock_orm.meeting_link = orm_kwargs["meeting_link"]
        mock_orm.is_all_day = orm_kwargs["is_all_day"]
        mock_orm.raw = orm_kwargs["raw"]

        result = CalendarEventConverter.orm_to_info(mock_orm, provider="google")

        assert result.id == original.id, "ID preserved"
        assert result.title == original.title, "Title preserved"
        assert result.start_time == original.start_time, "Start time preserved"
        assert result.end_time == original.end_time, "End time preserved"
        assert result.attendees == original.attendees, "Attendees preserved"
        assert result.location == original.location, "Location preserved"
        assert result.description == original.description, "Description preserved"
        assert result.meeting_url == original.meeting_url, "Meeting URL preserved"
        assert result.is_all_day == original.is_all_day, "is_all_day preserved"
        assert result.provider == original.provider, "Provider preserved"
        assert result.raw == original.raw, "Raw preserved"
