"""Tests for infrastructure converters."""

from __future__ import annotations

from unittest.mock import MagicMock
from uuid import uuid4

import pytest

from noteflow.domain import entities
from noteflow.domain.entities.named_entity import EntityCategory, NamedEntity
from noteflow.domain.value_objects import MeetingId
from noteflow.infrastructure.asr import dto
from noteflow.infrastructure.converters import AsrConverter, OrmConverter
from noteflow.infrastructure.converters.ner_converters import NerConverter


class TestAsrConverter:
    """Tests for AsrConverter."""

    @pytest.mark.parametrize(
        "attr,expected",
        [("word", "hello"), ("start_time", 1.5), ("end_time", 2.0), ("probability", 0.95)],
    )
    def test_word_timing_to_domain_maps_field_names(self, attr: str, expected: object) -> None:
        """Test ASR start/end maps to domain start_time/end_time."""
        asr_word = dto.WordTiming(word="hello", start=1.5, end=2.0, probability=0.95)
        result = AsrConverter.word_timing_to_domain(asr_word)
        assert getattr(result, attr) == expected, f"{attr} should equal {expected}"

    def test_word_timing_to_domain_preserves_precision(self) -> None:
        """Test timing values preserve floating point precision."""
        asr_word = dto.WordTiming(
            word="test",
            start=0.123456789,
            end=0.987654321,
            probability=0.999999,
        )

        result = AsrConverter.word_timing_to_domain(asr_word)

        assert result.start_time == 0.123456789, "start_time precision preserved"
        assert result.end_time == 0.987654321, "end_time precision preserved"
        assert result.probability == 0.999999, "probability precision preserved"

    def test_word_timing_to_domain_returns_domain_type(self) -> None:
        """Test converter returns domain WordTiming type."""
        asr_word = dto.WordTiming(word="test", start=1.0, end=2.0, probability=0.9)

        result = AsrConverter.word_timing_to_domain(asr_word)

        assert isinstance(result, entities.WordTiming), "result should be domain WordTiming type"

    @pytest.mark.parametrize(
        "idx,attr,expected",
        [(0, "word", "hello"), (0, "start_time", 0.0), (1, "word", "world"), (1, "start_time", 1.0)],
    )
    def test_result_to_domain_words_converts_all(
        self, idx: int, attr: str, expected: object
    ) -> None:
        """Test batch conversion of ASR result words."""
        asr_result = dto.AsrResult(
            text="hello world",
            start=0.0,
            end=2.0,
            words=(
                dto.WordTiming(word="hello", start=0.0, end=1.0, probability=0.9),
                dto.WordTiming(word="world", start=1.0, end=2.0, probability=0.95),
            ),
        )
        words = AsrConverter.result_to_domain_words(asr_result)
        assert getattr(words[idx], attr) == expected, f"word[{idx}].{attr} should equal {expected}"

    def test_result_to_domain_words_count(self) -> None:
        """Test batch conversion returns correct count."""
        asr_result = dto.AsrResult(
            text="hello world",
            start=0.0,
            end=2.0,
            words=(
                dto.WordTiming(word="hello", start=0.0, end=1.0, probability=0.9),
                dto.WordTiming(word="world", start=1.0, end=2.0, probability=0.95),
            ),
        )
        words = AsrConverter.result_to_domain_words(asr_result)
        assert len(words) == 2, "should convert both words"

    def test_result_to_domain_words_empty(self) -> None:
        """Test conversion with empty words tuple."""
        asr_result = dto.AsrResult(text="", start=0.0, end=0.0, words=())

        words = AsrConverter.result_to_domain_words(asr_result)

        assert words == [], "empty words tuple yields empty list"


class TestOrmConverterToOrmKwargs:
    """Tests for OrmConverter.word_timing_to_orm_kwargs."""

    def test_converts_to_dict(self) -> None:
        """Test domain to ORM kwargs conversion."""
        word = entities.WordTiming(
            word="test",
            start_time=1.5,
            end_time=2.0,
            probability=0.9,
        )

        result = OrmConverter.word_timing_to_orm_kwargs(word, word_index=0)

        expected = {
            "word": "test",
            "start_time": 1.5,
            "end_time": 2.0,
            "probability": 0.9,
            "word_index": 0,
        }
        assert result == expected, "all fields correctly converted to ORM kwargs"

    def test_preserves_precision(self) -> None:
        """Test floating point precision in kwargs."""
        word = entities.WordTiming(
            word="precise",
            start_time=0.123456789,
            end_time=0.987654321,
            probability=0.111111,
        )

        result = OrmConverter.word_timing_to_orm_kwargs(word, word_index=5)

        assert result["start_time"] == 0.123456789, "Start time precision preserved"
        assert result["end_time"] == 0.987654321, "End time precision preserved"
        assert result["probability"] == 0.111111, "Probability precision preserved"
        assert result["word_index"] == 5, "Word index preserved"


class TestNerConverterToOrmKwargs:
    """Tests for NerConverter.to_orm_kwargs."""

    def test_ner_entity_to_orm_kwargs(self) -> None:
        """Convert domain NamedEntity to ORM kwargs dict."""
        meeting_id = MeetingId(uuid4())
        entity = NamedEntity(
            id=uuid4(),
            meeting_id=meeting_id,
            text="Acme Corp",
            normalized_text="acme corp",
            category=EntityCategory.COMPANY,
            segment_ids=[0, 1, 2],
            confidence=0.95,
            is_pinned=True,
        )

        result = NerConverter.to_orm_kwargs(entity)

        assert result["id"] == entity.id, "ID should be preserved"
        assert result["meeting_id"] == meeting_id, "Meeting ID should be preserved"
        assert result["text"] == "Acme Corp", "Text should be preserved"
        assert result["normalized_text"] == "acme corp", "Normalized text should be preserved"
        assert result["category"] == "company", "Category should be string value"
        assert result["segment_ids"] == [0, 1, 2], "Segment IDs should be preserved"
        assert result["confidence"] == 0.95, "Confidence should be preserved"
        assert result["is_pinned"] is True, "is_pinned should be preserved"

    def test_converts_category_to_string_value(self) -> None:
        """Category enum is converted to its string value."""
        entity = NamedEntity(
            text="John",
            category=EntityCategory.PERSON,
            confidence=0.8,
        )

        result = NerConverter.to_orm_kwargs(entity)

        assert result["category"] == "person", "category value should be 'person'"
        assert isinstance(result["category"], str), "category should be string type"

    @pytest.mark.parametrize(
        ("category", "expected_value"),
        [
            (EntityCategory.PERSON, "person"),
            (EntityCategory.COMPANY, "company"),
            (EntityCategory.PRODUCT, "product"),
            (EntityCategory.LOCATION, "location"),
            (EntityCategory.DATE, "date"),
            (EntityCategory.OTHER, "other"),
        ],
    )
    def test_all_category_values_convert(
        self, category: EntityCategory, expected_value: str
    ) -> None:
        """All category enum values convert to correct string."""
        entity = NamedEntity(text="Test", category=category, confidence=0.5)
        result = NerConverter.to_orm_kwargs(entity)
        assert result["category"] == expected_value, f"category {category} should convert to '{expected_value}'"


class TestNerConverterOrmToDomain:
    """Tests for NerConverter.orm_to_domain."""

    @pytest.fixture
    def mock_orm_model(self) -> MagicMock:
        """Create mock ORM model with typical values."""
        model = MagicMock()
        model.id = uuid4()
        model.meeting_id = uuid4()
        model.text = "Google Inc."
        model.normalized_text = "google inc."
        model.category = "company"
        model.segment_ids = [1, 3, 5]
        model.confidence = 0.92
        model.is_pinned = False
        return model

    def test_ner_orm_to_domain(self, mock_orm_model: MagicMock) -> None:
        """Convert ORM model to domain NamedEntity."""
        result = NerConverter.orm_to_domain(mock_orm_model)

        assert isinstance(result, NamedEntity), "Should return NamedEntity instance"
        assert result.id == mock_orm_model.id, "ID should match ORM model"
        assert result.meeting_id == MeetingId(mock_orm_model.meeting_id), "Meeting ID should match"
        assert result.text == "Google Inc.", "Text should match ORM model"
        assert result.normalized_text == "google inc.", "Normalized text should match"
        assert result.category == EntityCategory.COMPANY, "Category should convert to enum"
        assert result.segment_ids == [1, 3, 5], "Segment IDs should match"
        assert result.confidence == 0.92, "Confidence should match"
        assert result.is_pinned is False, "is_pinned should match"

    def test_sets_db_id_from_orm_id(self, mock_orm_model: MagicMock) -> None:
        """Domain entity db_id is set from ORM id."""
        result = NerConverter.orm_to_domain(mock_orm_model)
        assert result.db_id == mock_orm_model.id, "db_id should match ORM model id"

    def test_converts_category_string_to_enum(self, mock_orm_model: MagicMock) -> None:
        """Category string from ORM is converted to EntityCategory enum."""
        mock_orm_model.category = "person"
        result = NerConverter.orm_to_domain(mock_orm_model)
        assert result.category == EntityCategory.PERSON, "category should convert to PERSON enum"
        assert isinstance(result.category, EntityCategory), "category should be EntityCategory type"

    def test_handles_none_segment_ids(self, mock_orm_model: MagicMock) -> None:
        """Null segment_ids in ORM becomes empty list in domain."""
        mock_orm_model.segment_ids = None
        result = NerConverter.orm_to_domain(mock_orm_model)
        assert result.segment_ids == [], "None segment_ids should become empty list"

    def test_handles_empty_segment_ids(self, mock_orm_model: MagicMock) -> None:
        """Empty segment_ids in ORM becomes empty list in domain."""
        mock_orm_model.segment_ids = []
        result = NerConverter.orm_to_domain(mock_orm_model)
        assert result.segment_ids == [], "empty segment_ids should remain empty list"


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

    def test_domain_to_orm_to_domain_preserves_values(self) -> None:
        """Round-trip conversion preserves all field values."""
        original = NamedEntity(
            id=uuid4(),
            meeting_id=MeetingId(uuid4()),
            text="Microsoft Corporation",
            normalized_text="microsoft corporation",
            category=EntityCategory.COMPANY,
            segment_ids=[0, 5, 10],
            confidence=0.88,
            is_pinned=True,
        )

        # Convert to ORM kwargs and simulate ORM model
        orm_kwargs = NerConverter.to_orm_kwargs(original)
        mock_orm = MagicMock()
        mock_orm.id = orm_kwargs["id"]
        mock_orm.meeting_id = orm_kwargs["meeting_id"]
        mock_orm.text = orm_kwargs["text"]
        mock_orm.normalized_text = orm_kwargs["normalized_text"]
        mock_orm.category = orm_kwargs["category"]
        mock_orm.segment_ids = orm_kwargs["segment_ids"]
        mock_orm.confidence = orm_kwargs["confidence"]
        mock_orm.is_pinned = orm_kwargs["is_pinned"]

        # Convert back to domain
        result = NerConverter.orm_to_domain(mock_orm)

        assert result.id == original.id, "ID preserved through round-trip"
        assert result.meeting_id == original.meeting_id, "Meeting ID preserved"
        assert result.text == original.text, "Text preserved"
        assert result.normalized_text == original.normalized_text, "Normalized text preserved"
        assert result.category == original.category, "Category preserved"
        assert result.segment_ids == original.segment_ids, "Segment IDs preserved"
        assert result.confidence == original.confidence, "Confidence preserved"
        assert result.is_pinned == original.is_pinned, "is_pinned preserved"
