"""Tests for OrmConverter 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 import (
    ActionItem,
    Annotation,
    KeyPoint,
    Meeting,
    Segment,
    Summary,
    WordTiming,
)
from noteflow.domain.value_objects import AnnotationType, MeetingId, MeetingState
from noteflow.infrastructure.converters import OrmConverter


class TestOrmConverterWordTimingToDomain:
    """Tests for OrmConverter.word_timing_to_domain."""

    @pytest.fixture
    def mock_word_timing_model(self) -> MagicMock:
        """Create mock ORM WordTimingModel with typical values."""
        model = MagicMock()
        model.word = "hello"
        model.start_time = 1.5
        model.end_time = 2.0
        model.probability = 0.95
        return model

    def test_converts_orm_to_domain_word_timing(
        self, mock_word_timing_model: MagicMock
    ) -> None:
        """Convert ORM model to domain WordTiming."""
        result = OrmConverter.word_timing_to_domain(mock_word_timing_model)

        assert isinstance(result, WordTiming), "Should return WordTiming"
        assert result.word == "hello", "Word should match"
        assert result.start_time == mock_word_timing_model.start_time, "Start time should match"
        assert result.end_time == mock_word_timing_model.end_time, "End time should match"
        assert result.probability == mock_word_timing_model.probability, "Probability should match"

    def test_preserves_timing_precision(
        self, mock_word_timing_model: MagicMock
    ) -> None:
        """Timing values preserve floating point precision."""
        mock_word_timing_model.start_time = 0.123456789
        mock_word_timing_model.end_time = 0.987654321
        mock_word_timing_model.probability = 0.999999

        result = OrmConverter.word_timing_to_domain(mock_word_timing_model)

        assert result.start_time == mock_word_timing_model.start_time, "Start time precision preserved"
        assert result.end_time == mock_word_timing_model.end_time, "End time precision preserved"
        assert result.probability == mock_word_timing_model.probability, "Probability precision preserved"


class TestOrmConverterMeetingToDomain:
    """Tests for OrmConverter.meeting_to_domain."""

    @pytest.fixture
    def mock_meeting_model(self) -> MagicMock:
        """Create mock ORM MeetingModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.title = "Team Standup"
        model.state = 2  # MeetingState.RECORDING
        model.created_at = datetime(2024, 1, 15, 9, 0, 0, tzinfo=UTC)
        model.started_at = datetime(2024, 1, 15, 9, 5, 0, tzinfo=UTC)
        model.ended_at = None
        model.metadata_ = {"source": "web", "version": "1.0"}
        model.wrapped_dek = b"encrypted-key"
        model.asset_path = "/data/meetings/abc123"
        model.version = 3
        return model

    def test_converts_orm_to_domain_meeting(
        self, mock_meeting_model: MagicMock
    ) -> None:
        """Convert ORM model to domain Meeting."""
        result = OrmConverter.meeting_to_domain(mock_meeting_model)

        assert isinstance(result, Meeting), "Should return Meeting"
        assert result.id == MeetingId(mock_meeting_model.id), "ID should match"
        assert result.title == "Team Standup", "Title should match"
        assert result.state == MeetingState.RECORDING, "State should be enum"
        assert result.created_at == mock_meeting_model.created_at, "Created at should match"
        assert result.started_at == mock_meeting_model.started_at, "Started at should match"
        assert result.ended_at is None, "Ended at should be None"
        assert result.asset_path == mock_meeting_model.asset_path, "Asset path should match"
        assert result.version == mock_meeting_model.version, "Version should match"

    @pytest.mark.parametrize(
        ("state_value", "expected_enum"),
        [
            (1, MeetingState.CREATED),
            (2, MeetingState.RECORDING),
            (3, MeetingState.STOPPED),
            (4, MeetingState.COMPLETED),
            (5, MeetingState.ERROR),
            (6, MeetingState.STOPPING),
        ],
    )
    def test_all_state_values_convert_correctly(
        self,
        mock_meeting_model: MagicMock,
        state_value: int,
        expected_enum: MeetingState,
    ) -> None:
        """All MeetingState values convert to correct enum values."""
        mock_meeting_model.state = state_value
        result = OrmConverter.meeting_to_domain(mock_meeting_model)
        assert result.state == expected_enum, f"State {state_value} should convert to {expected_enum}"

    def test_converts_metadata_values_to_strings(
        self, mock_meeting_model: MagicMock
    ) -> None:
        """Metadata values are converted to strings."""
        mock_meeting_model.metadata_ = {"count": 42, "active": True, "rate": 3.14}

        result = OrmConverter.meeting_to_domain(mock_meeting_model)

        assert result.metadata == {"count": "42", "active": "True", "rate": "3.14"}, "Metadata values should be stringified"

    def test_handles_empty_metadata(self, mock_meeting_model: MagicMock) -> None:
        """Empty metadata dict is preserved."""
        mock_meeting_model.metadata_ = {}
        result = OrmConverter.meeting_to_domain(mock_meeting_model)
        assert result.metadata == {}, "Empty metadata should be preserved"

    def test_preserves_wrapped_dek(self, mock_meeting_model: MagicMock) -> None:
        """Wrapped DEK bytes are preserved."""
        result = OrmConverter.meeting_to_domain(mock_meeting_model)
        assert result.wrapped_dek == b"encrypted-key", "Wrapped DEK bytes should be preserved"


class TestOrmConverterSegmentToDomain:
    """Tests for OrmConverter.segment_to_domain."""

    @pytest.fixture
    def mock_segment_model(self) -> MagicMock:
        """Create mock ORM SegmentModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.segment_id = 5
        model.meeting_id = uuid4()
        model.text = "This is a test segment."
        model.start_time = 10.0
        model.end_time = 15.5
        model.language = "en"
        model.language_confidence = 0.98
        model.avg_logprob = -0.5
        model.no_speech_prob = 0.01
        model.embedding = [0.1, 0.2, 0.3, 0.4]
        model.speaker_id = "speaker-1"
        model.speaker_confidence = 0.85
        model.words = []  # Empty by default
        return model

    def test_converts_orm_to_domain_segment(
        self, mock_segment_model: MagicMock
    ) -> None:
        """Convert ORM model to domain Segment."""
        result = OrmConverter.segment_to_domain(mock_segment_model)

        assert isinstance(result, Segment), "Should return Segment"
        assert result.segment_id == mock_segment_model.segment_id, "Segment ID should match"
        assert result.text == mock_segment_model.text, "Text should match"
        assert result.start_time == mock_segment_model.start_time, "Start time should match"
        assert result.end_time == mock_segment_model.end_time, "End time should match"
        assert result.meeting_id == MeetingId(mock_segment_model.meeting_id), "Meeting ID should match"
        assert result.language == "en", "Language should match"
        assert result.language_confidence == 0.98, "Language confidence should match"
        assert result.speaker_id == "speaker-1", "Speaker ID should match"
        assert result.db_id == mock_segment_model.id, "DB ID should match"

    def test_converts_words_when_include_words_true(
        self, mock_segment_model: MagicMock
    ) -> None:
        """Words are converted when include_words is True."""
        word1 = MagicMock()
        word1.word = "This"
        word1.start_time = 10.0
        word1.end_time = 10.5
        word1.probability = 0.9

        word2 = MagicMock()
        word2.word = "is"
        word2.start_time = 10.5
        word2.end_time = 11.0
        word2.probability = 0.95

        mock_segment_model.words = [word1, word2]

        result = OrmConverter.segment_to_domain(mock_segment_model, include_words=True)

        expected_word_count = len(mock_segment_model.words)
        assert len(result.words) == expected_word_count, "Should have 2 words"
        assert result.words[0].word == "This", "First word should be 'This'"
        assert result.words[1].word == "is", "Second word should be 'is'"

    def test_excludes_words_when_include_words_false(
        self, mock_segment_model: MagicMock
    ) -> None:
        """Words are excluded when include_words is False."""
        word = MagicMock()
        word.word = "Test"
        word.start_time = 10.0
        word.end_time = 10.5
        word.probability = 0.9
        mock_segment_model.words = [word]

        result = OrmConverter.segment_to_domain(mock_segment_model, include_words=False)

        assert result.words == [], "Words should be empty"

    def test_converts_embedding_to_list(
        self, mock_segment_model: MagicMock
    ) -> None:
        """Embedding is converted to list."""
        result = OrmConverter.segment_to_domain(mock_segment_model)

        assert isinstance(result.embedding, list), "Embedding should be list"
        assert result.embedding == [0.1, 0.2, 0.3, 0.4], "Embedding values should match"

    def test_handles_none_embedding(self, mock_segment_model: MagicMock) -> None:
        """None embedding remains None in domain."""
        mock_segment_model.embedding = None
        result = OrmConverter.segment_to_domain(mock_segment_model)
        assert result.embedding is None, "None embedding should remain None"


class TestOrmConverterAnnotationToDomain:
    """Tests for OrmConverter.annotation_to_domain."""

    @pytest.fixture
    def mock_annotation_model(self) -> MagicMock:
        """Create mock ORM AnnotationModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.annotation_id = uuid4()
        model.meeting_id = uuid4()
        model.annotation_type = "note"
        model.text = "Important point about budget"
        model.start_time = 120.5
        model.end_time = 125.0
        model.segment_ids = [10, 11, 12]
        model.created_at = datetime(2024, 1, 15, 10, 0, 0, tzinfo=UTC)
        return model

    def test_converts_orm_to_domain_annotation(
        self, mock_annotation_model: MagicMock
    ) -> None:
        """Convert ORM model to domain Annotation."""
        result = OrmConverter.annotation_to_domain(mock_annotation_model)

        assert isinstance(result, Annotation), "Should return Annotation"
        assert result.id == mock_annotation_model.annotation_id, "Annotation ID should match"
        assert result.meeting_id == MeetingId(mock_annotation_model.meeting_id), "Meeting ID should match"
        assert result.annotation_type == AnnotationType.NOTE, "Annotation type should be NOTE"
        assert result.text == "Important point about budget", "Text should match"
        assert result.start_time == mock_annotation_model.start_time, "Start time should match"
        assert result.end_time == mock_annotation_model.end_time, "End time should match"
        assert result.segment_ids == mock_annotation_model.segment_ids, "Segment IDs should match"
        assert result.db_id == mock_annotation_model.id, "DB ID should match"

    @pytest.mark.parametrize(
        ("type_string", "expected_enum"),
        [
            ("action_item", AnnotationType.ACTION_ITEM),
            ("decision", AnnotationType.DECISION),
            ("note", AnnotationType.NOTE),
            ("risk", AnnotationType.RISK),
        ],
    )
    def test_all_annotation_types_convert_correctly(
        self,
        mock_annotation_model: MagicMock,
        type_string: str,
        expected_enum: AnnotationType,
    ) -> None:
        """All AnnotationType strings convert to correct enum values."""
        mock_annotation_model.annotation_type = type_string
        result = OrmConverter.annotation_to_domain(mock_annotation_model)
        assert result.annotation_type == expected_enum, f"Type '{type_string}' should convert to {expected_enum}"


class TestOrmConverterKeyPointToDomain:
    """Tests for OrmConverter.key_point_to_domain."""

    @pytest.fixture
    def mock_key_point_model(self) -> MagicMock:
        """Create mock ORM KeyPointModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.text = "Revenue increased by 15% this quarter"
        model.segment_ids = [5, 6, 7]
        model.start_time = 45.0
        model.end_time = 52.0
        model.position = 1
        return model

    def test_converts_orm_to_domain_key_point(
        self, mock_key_point_model: MagicMock
    ) -> None:
        """Convert ORM model to domain KeyPoint."""
        result = OrmConverter.key_point_to_domain(mock_key_point_model)

        assert isinstance(result, KeyPoint), "Should return KeyPoint"
        assert result.text == "Revenue increased by 15% this quarter", "Text should match"
        assert result.segment_ids == [5, 6, 7], "Segment IDs should match"
        assert result.start_time == mock_key_point_model.start_time, "Start time should match"
        assert result.end_time == mock_key_point_model.end_time, "End time should match"
        assert result.position == mock_key_point_model.position, "Position should match"
        assert result.db_id == mock_key_point_model.id, "DB ID should match"


class TestOrmConverterActionItemToDomain:
    """Tests for OrmConverter.action_item_to_domain."""

    @pytest.fixture
    def mock_action_item_model(self) -> MagicMock:
        """Create mock ORM ActionItemModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.text = "Schedule follow-up meeting with stakeholders"
        model.assignee = "John Smith"
        model.due_date = datetime(2024, 1, 22, 17, 0, 0, tzinfo=UTC)
        model.priority = "high"
        model.segment_ids = [20, 21]
        model.start_time = 180.0
        model.end_time = 195.5
        model.position = 0
        return model

    def test_converts_orm_to_domain_action_item(
        self, mock_action_item_model: MagicMock
    ) -> None:
        """Convert ORM model to domain ActionItem."""
        result = OrmConverter.action_item_to_domain(mock_action_item_model)

        assert isinstance(result, ActionItem), "Should return ActionItem"
        assert result.text == "Schedule follow-up meeting with stakeholders", "Text should match"
        assert result.assignee == "John Smith", "Assignee should match"
        assert result.due_date == mock_action_item_model.due_date, "Due date should match"
        assert result.priority == "high", "Priority should match"
        assert result.segment_ids == mock_action_item_model.segment_ids, "Segment IDs should match"
        assert result.start_time == mock_action_item_model.start_time, "Start time should match"
        assert result.end_time == mock_action_item_model.end_time, "End time should match"
        assert result.position == mock_action_item_model.position, "Position should match"
        assert result.db_id == mock_action_item_model.id, "DB ID should match"

    def test_handles_none_optional_fields(
        self, mock_action_item_model: MagicMock
    ) -> None:
        """None values for optional fields are preserved."""
        mock_action_item_model.assignee = None
        mock_action_item_model.due_date = None
        mock_action_item_model.priority = None

        result = OrmConverter.action_item_to_domain(mock_action_item_model)

        assert result.assignee is None, "Assignee should be None"
        assert result.due_date is None, "Due date should be None"
        assert result.priority is None, "Priority should be None"


class TestOrmConverterSummaryToDomain:
    """Tests for OrmConverter.summary_to_domain."""

    @pytest.fixture
    def mock_summary_model(self) -> MagicMock:
        """Create mock ORM SummaryModel with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.executive_summary = "The team discussed Q4 results and planned for Q1."
        model.generated_at = datetime(2024, 1, 15, 11, 0, 0, tzinfo=UTC)
        model.provider_name = "anthropic"
        model.model_name = "claude-3-sonnet"
        model.tokens_used = 1500
        model.latency_ms = 2500
        model.verification = {"verified": True, "confidence": 0.9}

        # Mock key points
        kp1 = MagicMock()
        kp1.id = uuid4()
        kp1.text = "Q4 revenue exceeded targets"
        kp1.segment_ids = [1, 2]
        kp1.start_time = 10.0
        kp1.end_time = 20.0
        kp1.position = 0

        kp2 = MagicMock()
        kp2.id = uuid4()
        kp2.text = "New product launch planned for February"
        kp2.segment_ids = [5, 6, 7]
        kp2.start_time = 45.0
        kp2.end_time = 60.0
        kp2.position = 1

        model.key_points = [kp1, kp2]

        # Mock action items
        ai1 = MagicMock()
        ai1.id = uuid4()
        ai1.text = "Finalize Q1 budget"
        ai1.assignee = "Finance Team"
        ai1.due_date = datetime(2024, 1, 31, tzinfo=UTC)
        ai1.priority = "high"
        ai1.segment_ids = [10]
        ai1.start_time = 90.0
        ai1.end_time = 95.0
        ai1.position = 0

        model.action_items = [ai1]

        return model

    def test_converts_orm_to_domain_summary(
        self, mock_summary_model: MagicMock
    ) -> None:
        """Convert ORM model to domain Summary."""
        meeting_id = MeetingId(uuid4())

        result = OrmConverter.summary_to_domain(mock_summary_model, meeting_id)

        assert isinstance(result, Summary), "Should return Summary"
        assert result.meeting_id == meeting_id, "Meeting ID should match"
        assert result.executive_summary == "The team discussed Q4 results and planned for Q1.", "Executive summary should match"
        assert result.generated_at == mock_summary_model.generated_at, "Generated at should match"
        assert result.provider_name == "anthropic", "Provider should match"
        assert result.model_name == "claude-3-sonnet", "Model should match"
        assert result.tokens_used == mock_summary_model.tokens_used, "Tokens should match"
        assert result.latency_ms == mock_summary_model.latency_ms, "Latency should match"
        assert result.db_id == mock_summary_model.id, "DB ID should match"

    def test_converts_key_points(self, mock_summary_model: MagicMock) -> None:
        """Key points are converted to domain entities."""
        meeting_id = MeetingId(uuid4())

        result = OrmConverter.summary_to_domain(mock_summary_model, meeting_id)

        expected_key_points_count = len(mock_summary_model.key_points)
        assert len(result.key_points) == expected_key_points_count, "Should have 2 key points"
        assert result.key_points[0].text == "Q4 revenue exceeded targets", "First key point text should match"
        assert result.key_points[1].text == "New product launch planned for February", "Second key point text should match"

    def test_converts_action_items(self, mock_summary_model: MagicMock) -> None:
        """Action items are converted to domain entities."""
        meeting_id = MeetingId(uuid4())

        result = OrmConverter.summary_to_domain(mock_summary_model, meeting_id)

        expected_action_items_count = len(mock_summary_model.action_items)
        assert len(result.action_items) == expected_action_items_count, "Should have 1 action item"
        assert result.action_items[0].text == "Finalize Q1 budget", "Action item text should match"
        assert result.action_items[0].assignee == "Finance Team", "Action item assignee should match"

    def test_converts_verification_dict(self, mock_summary_model: MagicMock) -> None:
        """Verification is converted to dict."""
        meeting_id = MeetingId(uuid4())

        result = OrmConverter.summary_to_domain(mock_summary_model, meeting_id)

        assert isinstance(result.verification, dict), "Verification should be dict"
        assert result.verification["verified"] is True, "Verified flag should be True"
        assert result.verification["confidence"] == 0.9, "Confidence should match"

    def test_handles_none_executive_summary(
        self, mock_summary_model: MagicMock
    ) -> None:
        """None executive_summary becomes empty string."""
        mock_summary_model.executive_summary = None
        meeting_id = MeetingId(uuid4())

        result = OrmConverter.summary_to_domain(mock_summary_model, meeting_id)

        assert result.executive_summary == "", "Should be empty string"

    def test_handles_empty_key_points(self, mock_summary_model: MagicMock) -> None:
        """Empty key points list is preserved."""
        mock_summary_model.key_points = []
        meeting_id = MeetingId(uuid4())

        result = OrmConverter.summary_to_domain(mock_summary_model, meeting_id)

        assert result.key_points == [], "Key points should be empty"

    def test_handles_empty_action_items(self, mock_summary_model: MagicMock) -> None:
        """Empty action items list is preserved."""
        mock_summary_model.action_items = []
        meeting_id = MeetingId(uuid4())

        result = OrmConverter.summary_to_domain(mock_summary_model, meeting_id)

        assert result.action_items == [], "Action items should be empty"
