"""Tests for IntegrationConverter and SyncRunConverter 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.entities.integration import (
    Integration,
    IntegrationStatus,
    IntegrationType,
    SyncRun,
    SyncRunStatus,
)
from noteflow.infrastructure.converters.integration_converters import (
    IntegrationConverter,
    SyncRunConverter,
)

# Test constants for sync run metrics
SYNC_RUN_ITEMS_SYNCED = 15
"""Number of items synced in a standard test sync run fixture."""

SYNC_RUN_DURATION_MS_SHORT = 5000
"""Short sync run duration in milliseconds (5 seconds)."""

SYNC_RUN_DURATION_MS_MEDIUM = 10000
"""Medium sync run duration in milliseconds (10 seconds)."""

SYNC_RUN_ITEMS_COMPLETE = 25
"""Number of items in a complete sync run test case."""


class TestIntegrationConverterOrmToDomain:
    """Tests for IntegrationConverter.orm_to_domain."""

    @pytest.fixture
    def mock_integration_model(self) -> MagicMock:
        """Create mock ORM IntegrationModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.workspace_id = uuid4()
        model.name = "Google Calendar"
        model.type = "calendar"
        model.status = "connected"
        model.config = {"provider_email": "user@example.com", "refresh_token": "xyz"}
        model.last_sync = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
        model.error_message = None
        model.created_at = datetime(2024, 1, 10, 10, 0, 0, tzinfo=UTC)
        model.updated_at = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
        return model

    def test_integration_orm_to_domain(
        self, mock_integration_model: MagicMock
    ) -> None:
        """Convert ORM model to domain Integration."""
        result = IntegrationConverter.orm_to_domain(mock_integration_model)

        assert isinstance(result, Integration), "Should return Integration instance"
        assert result.id == mock_integration_model.id, "ID should match"
        assert result.workspace_id == mock_integration_model.workspace_id, "Workspace ID should match"
        assert result.name == "Google Calendar", "Name should match"
        assert result.type == IntegrationType.CALENDAR, "Type should be enum"
        assert result.status == IntegrationStatus.CONNECTED, "Status should be enum"
        assert result.last_sync == mock_integration_model.last_sync, "Last sync should match"
        assert result.error_message is None, "Error message should be None"

    def test_converts_type_string_to_enum(
        self, mock_integration_model: MagicMock
    ) -> None:
        """Type string is converted to IntegrationType enum."""
        mock_integration_model.type = "email"
        result = IntegrationConverter.orm_to_domain(mock_integration_model)
        assert result.type == IntegrationType.EMAIL, "Type should be EMAIL enum"
        assert isinstance(result.type, IntegrationType), "Type should be IntegrationType instance"

    @pytest.mark.parametrize(
        ("type_string", "expected_enum"),
        [
            ("auth", IntegrationType.AUTH),
            ("email", IntegrationType.EMAIL),
            ("calendar", IntegrationType.CALENDAR),
            ("pkm", IntegrationType.PKM),
            ("custom", IntegrationType.CUSTOM),
        ],
    )
    def test_all_type_strings_convert_correctly(
        self,
        mock_integration_model: MagicMock,
        type_string: str,
        expected_enum: IntegrationType,
    ) -> None:
        """All IntegrationType strings convert to correct enum values."""
        mock_integration_model.type = type_string
        result = IntegrationConverter.orm_to_domain(mock_integration_model)
        assert result.type == expected_enum, f"Type string '{type_string}' should convert to {expected_enum}"

    @pytest.mark.parametrize(
        ("status_string", "expected_enum"),
        [
            ("disconnected", IntegrationStatus.DISCONNECTED),
            ("connected", IntegrationStatus.CONNECTED),
            ("error", IntegrationStatus.ERROR),
        ],
    )
    def test_integration_status_strings_convert(
        self,
        mock_integration_model: MagicMock,
        status_string: str,
        expected_enum: IntegrationStatus,
    ) -> None:
        """All IntegrationStatus strings convert to correct enum values."""
        mock_integration_model.status = status_string
        result = IntegrationConverter.orm_to_domain(mock_integration_model)
        assert result.status == expected_enum, f"Status string '{status_string}' should convert to {expected_enum}"

    def test_converts_config_dict(self, mock_integration_model: MagicMock) -> None:
        """Config is converted to dict from ORM model."""
        result = IntegrationConverter.orm_to_domain(mock_integration_model)
        assert isinstance(result.config, dict), "Config should be dict"
        assert result.config["provider_email"] == "user@example.com", "Provider email should be preserved"

    def test_handles_none_config(self, mock_integration_model: MagicMock) -> None:
        """None config in ORM becomes empty dict in domain."""
        mock_integration_model.config = None
        result = IntegrationConverter.orm_to_domain(mock_integration_model)
        assert result.config == {}, "None config should become empty dict"

    def test_integration_handles_error_status(self, mock_integration_model: MagicMock) -> None:
        """Error status with error message is converted correctly."""
        mock_integration_model.status = "error"
        mock_integration_model.error_message = "Token expired"

        result = IntegrationConverter.orm_to_domain(mock_integration_model)

        assert result.status == IntegrationStatus.ERROR, "Status should be ERROR enum"
        assert result.error_message == "Token expired", "Error message should be preserved"


class TestIntegrationConverterToOrmKwargs:
    """Tests for IntegrationConverter.to_orm_kwargs."""

    @pytest.fixture
    def integration_entity(self) -> Integration:
        """Create domain Integration entity for testing."""
        return Integration(
            id=uuid4(),
            workspace_id=uuid4(),
            name="Outlook Calendar",
            type=IntegrationType.CALENDAR,
            status=IntegrationStatus.CONNECTED,
            config={"provider_email": "test@outlook.com"},
            last_sync=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
            error_message=None,
            created_at=datetime(2024, 1, 10, 10, 0, 0, tzinfo=UTC),
            updated_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )

    def test_integration_to_orm_kwargs(
        self, integration_entity: Integration
    ) -> None:
        """Convert domain Integration to ORM kwargs dict."""
        result = IntegrationConverter.to_orm_kwargs(integration_entity)

        assert result["id"] == integration_entity.id, "ID should be preserved"
        assert result["workspace_id"] == integration_entity.workspace_id, "Workspace ID should be preserved"
        assert result["name"] == "Outlook Calendar", "Name should be preserved"
        assert result["type"] == "calendar", "Type should be string value"
        assert result["status"] == "connected", "Status should be string value"
        assert result["config"] == {"provider_email": "test@outlook.com"}, "Config should be preserved"
        assert result["error_message"] is None, "Error message should be None"

    @pytest.mark.parametrize(
        ("type_enum", "expected_string"),
        [
            (IntegrationType.AUTH, "auth"),
            (IntegrationType.EMAIL, "email"),
            (IntegrationType.CALENDAR, "calendar"),
            (IntegrationType.PKM, "pkm"),
            (IntegrationType.CUSTOM, "custom"),
        ],
    )
    def test_all_type_enums_convert_to_strings(
        self, type_enum: IntegrationType, expected_string: str
    ) -> None:
        """All IntegrationType enums convert to correct string values."""
        integration = Integration(
            id=uuid4(),
            workspace_id=uuid4(),
            name="Test",
            type=type_enum,
            status=IntegrationStatus.DISCONNECTED,
        )
        result = IntegrationConverter.to_orm_kwargs(integration)
        assert result["type"] == expected_string, f"Type enum {type_enum} should convert to '{expected_string}'"

    @pytest.mark.parametrize(
        ("status_enum", "expected_string"),
        [
            (IntegrationStatus.DISCONNECTED, "disconnected"),
            (IntegrationStatus.CONNECTED, "connected"),
            (IntegrationStatus.ERROR, "error"),
        ],
    )
    def test_integration_status_enums_to_strings(
        self, status_enum: IntegrationStatus, expected_string: str
    ) -> None:
        """All IntegrationStatus enums convert to correct string values."""
        integration = Integration(
            id=uuid4(),
            workspace_id=uuid4(),
            name="Test",
            type=IntegrationType.CALENDAR,
            status=status_enum,
        )
        result = IntegrationConverter.to_orm_kwargs(integration)
        assert result["status"] == expected_string, f"Status enum {status_enum} should convert to '{expected_string}'"


class TestSyncRunConverterOrmToDomain:
    """Tests for SyncRunConverter.orm_to_domain."""

    @pytest.fixture
    def mock_sync_run_model(self) -> MagicMock:
        """Create mock ORM IntegrationSyncRunModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.integration_id = uuid4()
        model.status = "success"
        model.started_at = datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC)
        model.ended_at = datetime(2024, 1, 15, 12, 0, 5, tzinfo=UTC)
        model.duration_ms = SYNC_RUN_DURATION_MS_SHORT
        model.error_message = None
        model.stats = {"items_synced": 10, "items_total": SYNC_RUN_ITEMS_SYNCED}
        return model

    def test_sync_run_orm_to_domain(
        self, mock_sync_run_model: MagicMock
    ) -> None:
        """Convert ORM model to domain SyncRun."""
        result = SyncRunConverter.orm_to_domain(mock_sync_run_model)

        assert isinstance(result, SyncRun), "Should return SyncRun instance"
        assert result.id == mock_sync_run_model.id, "ID should match"
        assert result.integration_id == mock_sync_run_model.integration_id, "Integration ID should match"
        assert result.status == SyncRunStatus.SUCCESS, "Status should be enum"
        assert result.duration_ms == SYNC_RUN_DURATION_MS_SHORT, "Duration should match"
        assert result.error_message is None, "Error message should be None"

    @pytest.mark.parametrize(
        ("status_string", "expected_enum"),
        [
            ("running", SyncRunStatus.RUNNING),
            ("success", SyncRunStatus.SUCCESS),
            ("error", SyncRunStatus.ERROR),
        ],
    )
    def test_sync_run_status_strings_convert(
        self,
        mock_sync_run_model: MagicMock,
        status_string: str,
        expected_enum: SyncRunStatus,
    ) -> None:
        """All SyncRunStatus strings convert to correct enum values."""
        mock_sync_run_model.status = status_string
        result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
        assert result.status == expected_enum, f"Status string '{status_string}' should convert to {expected_enum}"

    def test_converts_stats_dict(self, mock_sync_run_model: MagicMock) -> None:
        """Stats is converted to dict from ORM model."""
        result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
        assert isinstance(result.stats, dict), "Stats should be dict"
        assert result.stats["items_synced"] == 10, "Items synced count should be preserved"
        assert result.stats["items_total"] == SYNC_RUN_ITEMS_SYNCED, "Items total count should be preserved"

    def test_handles_none_stats(self, mock_sync_run_model: MagicMock) -> None:
        """None stats in ORM becomes empty dict in domain."""
        mock_sync_run_model.stats = None
        result = SyncRunConverter.orm_to_domain(mock_sync_run_model)
        assert result.stats == {}, "None stats should become empty dict"

    def test_sync_run_handles_error_status(self, mock_sync_run_model: MagicMock) -> None:
        """Error status with error message is converted correctly."""
        mock_sync_run_model.status = "error"
        mock_sync_run_model.error_message = "API rate limit exceeded"

        result = SyncRunConverter.orm_to_domain(mock_sync_run_model)

        assert result.status == SyncRunStatus.ERROR, "Status should be ERROR enum"
        assert result.error_message == "API rate limit exceeded", "Error message should be preserved"


class TestSyncRunConverterToOrmKwargs:
    """Tests for SyncRunConverter.to_orm_kwargs."""

    def test_sync_run_to_orm_kwargs(self) -> None:
        """Convert domain SyncRun to ORM kwargs dict."""
        sync_run = SyncRun(
            id=uuid4(),
            integration_id=uuid4(),
            status=SyncRunStatus.SUCCESS,
            started_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
            ended_at=datetime(2024, 1, 15, 12, 0, 10, tzinfo=UTC),
            duration_ms=SYNC_RUN_DURATION_MS_MEDIUM,
            error_message=None,
            stats={"items_synced": SYNC_RUN_ITEMS_COMPLETE},
        )

        result = SyncRunConverter.to_orm_kwargs(sync_run)

        assert result["id"] == sync_run.id, "ID should be preserved"
        assert result["integration_id"] == sync_run.integration_id, "Integration ID should be preserved"
        assert result["status"] == "success", "Status should be string value"
        assert result["duration_ms"] == SYNC_RUN_DURATION_MS_MEDIUM, "Duration should be preserved"
        assert result["stats"] == {"items_synced": SYNC_RUN_ITEMS_COMPLETE}, "Stats should be preserved"

    @pytest.mark.parametrize(
        ("status_enum", "expected_string"),
        [
            (SyncRunStatus.RUNNING, "running"),
            (SyncRunStatus.SUCCESS, "success"),
            (SyncRunStatus.ERROR, "error"),
        ],
    )
    def test_sync_run_status_enums_to_strings(
        self, status_enum: SyncRunStatus, expected_string: str
    ) -> None:
        """All SyncRunStatus enums convert to correct string values."""
        sync_run = SyncRun(
            id=uuid4(),
            integration_id=uuid4(),
            status=status_enum,
            started_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )
        result = SyncRunConverter.to_orm_kwargs(sync_run)
        assert result["status"] == expected_string, f"Status enum {status_enum} should convert to '{expected_string}'"


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

    def test_integration_domain_to_orm_to_domain_preserves_values(self) -> None:
        """Round-trip conversion preserves all Integration field values."""
        original = Integration(
            id=uuid4(),
            workspace_id=uuid4(),
            name="Round Trip Integration",
            type=IntegrationType.CALENDAR,
            status=IntegrationStatus.CONNECTED,
            config={"provider_email": "test@example.com", "scope": "calendar.read"},
            last_sync=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
            error_message=None,
            created_at=datetime(2024, 1, 10, 10, 0, 0, tzinfo=UTC),
            updated_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
        )

        orm_kwargs = IntegrationConverter.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.type = orm_kwargs["type"]
        mock_orm.status = orm_kwargs["status"]
        mock_orm.config = orm_kwargs["config"]
        mock_orm.last_sync = orm_kwargs["last_sync"]
        mock_orm.error_message = orm_kwargs["error_message"]
        mock_orm.created_at = orm_kwargs["created_at"]
        mock_orm.updated_at = orm_kwargs["updated_at"]

        result = IntegrationConverter.orm_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.type == original.type, "Type preserved"
        assert result.status == original.status, "Status preserved"
        assert result.config == original.config, "Config preserved"
        assert result.last_sync == original.last_sync, "Last sync preserved"

    def test_sync_run_domain_to_orm_to_domain_preserves_values(self) -> None:
        """Round-trip conversion preserves all SyncRun field values."""
        original = SyncRun(
            id=uuid4(),
            integration_id=uuid4(),
            status=SyncRunStatus.SUCCESS,
            started_at=datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC),
            ended_at=datetime(2024, 1, 15, 12, 0, 15, tzinfo=UTC),
            duration_ms=15000,
            error_message=None,
            stats={"items_synced": 50, "items_total": 50},
        )

        orm_kwargs = SyncRunConverter.to_orm_kwargs(original)
        mock_orm = MagicMock()
        mock_orm.id = orm_kwargs["id"]
        mock_orm.integration_id = orm_kwargs["integration_id"]
        mock_orm.status = orm_kwargs["status"]
        mock_orm.started_at = orm_kwargs["started_at"]
        mock_orm.ended_at = orm_kwargs["ended_at"]
        mock_orm.duration_ms = orm_kwargs["duration_ms"]
        mock_orm.error_message = orm_kwargs["error_message"]
        mock_orm.stats = orm_kwargs["stats"]

        result = SyncRunConverter.orm_to_domain(mock_orm)

        assert result.id == original.id, "ID preserved"
        assert result.integration_id == original.integration_id, "Integration ID preserved through round-trip"
        assert result.status == original.status, "Status preserved"
        assert result.duration_ms == original.duration_ms, "Duration preserved"
        assert result.stats == original.stats, "Stats preserved"
