"""Tests for mirror facts: synthetic fact creation for unmatched relations.

Covers:
- _create_mirror_fact: fact_text template, confidence, source_context, entity linkage
- Prompt improvement: FACT_EXTRACTION_PROMPT and EXTRACTION_SYSTEM_PROMPT include
  implicit fact extraction instructions
- Integration: upsert_relations creates mirror fact when match fails
"""

from __future__ import annotations

import json
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock

import pytest
from conftest import FakeEmbeddingProvider, FakeLLMProvider, run_async

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_resolved_entity(
    entity_key: str = "person:ana",
    display_name: str = "Ana",
    entity_type: str = "person",
) -> MagicMock:
    """Create a mock ResolvedEntity."""
    entity = MagicMock()
    entity.entity_key = entity_key
    entity.display_name = display_name
    entity.entity_type = entity_type
    entity.is_new = False
    entity.resolution_method = "exact"
    entity.fuzzy_score = None
    return entity


# ===========================================================================
# Tests: _create_mirror_fact
# ===========================================================================


class TestCreateMirrorFact:
    """Tests for the _create_mirror_fact function."""

    @pytest.mark.asyncio
    async def test_mirror_fact_text_template(self):
        """Mirror fact generates readable text from relation components."""
        from arandu.write.upsert import _create_mirror_fact

        source = _make_resolved_entity("person:ana", "Ana")
        target = _make_resolved_entity("place:curitiba", "Curitiba")
        embeddings = FakeEmbeddingProvider()
        now = datetime.now(UTC)

        session = AsyncMock()

        fact = await _create_mirror_fact(
            session,
            "user-1",
            "event-1",
            source,
            target,
            "lives_in",
            now,
            embeddings,
        )

        assert fact.fact_text == "Ana lives in Curitiba"

    @pytest.mark.asyncio
    async def test_mirror_fact_confidence_and_source_context(self):
        """Mirror fact has confidence=0.60 and source_context='inferred_from_relation'."""
        from arandu.write.upsert import _create_mirror_fact

        source = _make_resolved_entity("person:ana", "Ana")
        target = _make_resolved_entity("organization:acme", "Acme Corp")
        embeddings = FakeEmbeddingProvider()
        now = datetime.now(UTC)

        session = AsyncMock()

        fact = await _create_mirror_fact(
            session,
            "user-1",
            "event-1",
            source,
            target,
            "works_at",
            now,
            embeddings,
        )

        assert fact.confidence == 0.60
        assert fact.importance == 0.3
        assert fact.source_context == "inferred_from_relation"

    @pytest.mark.asyncio
    async def test_mirror_fact_entity_linkage(self):
        """Mirror fact is linked to the source entity."""
        from arandu.write.upsert import _create_mirror_fact

        source = _make_resolved_entity("person:ana", "Ana", "person")
        target = _make_resolved_entity("place:curitiba", "Curitiba", "place")
        embeddings = FakeEmbeddingProvider()
        now = datetime.now(UTC)

        session = AsyncMock()

        fact = await _create_mirror_fact(
            session,
            "user-1",
            "event-1",
            source,
            target,
            "lives_in",
            now,
            embeddings,
        )

        assert fact.entity_key == "person:ana"
        assert fact.entity_type == "person"
        assert fact.entity_name == "Ana"

    @pytest.mark.asyncio
    async def test_mirror_fact_rel_type_readable(self):
        """Underscores in rel_type are replaced with spaces in fact_text."""
        from arandu.write.upsert import _create_mirror_fact

        source = _make_resolved_entity("person:ana", "Ana")
        target = _make_resolved_entity("person:silva", "Professor Silva")
        embeddings = FakeEmbeddingProvider()
        now = datetime.now(UTC)

        session = AsyncMock()

        fact = await _create_mirror_fact(
            session,
            "user-1",
            "event-1",
            source,
            target,
            "mentored_by",
            now,
            embeddings,
        )

        assert fact.fact_text == "Ana mentored by Professor Silva"


# ===========================================================================
# Tests: Prompt improvement for implicit facts
# ===========================================================================


class TestPromptImplicitFacts:
    """Tests that extraction prompts instruct LLM to extract implicit facts."""

    def test_fact_extraction_prompt_has_relationship_fact_rule(self):
        """FACT_EXTRACTION_PROMPT includes rule that relations MUST also generate facts."""
        from arandu.write.extract import FACT_EXTRACTION_PROMPT

        assert "relationship" in FACT_EXTRACTION_PROMPT.lower() and "fact" in FACT_EXTRACTION_PROMPT.lower()

    def test_extraction_with_implicit_fact(self):
        """LLM can return an implicit fact alongside a relation."""
        from arandu.write.extract import extract_from_message

        response = json.dumps(
            {
                "entities": [
                    {"name": "user", "type": "person"},
                    {"name": "Mom", "type": "person"},
                    {"name": "Curitiba", "type": "place"},
                ],
                "facts": [
                    {"subject": "user", "fact": "User is visiting mom in Curitiba", "confidence": 0.95},
                    {"subject": "Mom", "fact": "User's mom lives in Curitiba", "confidence": 0.60},
                ],
                "relations": [
                    {"source": "Mom", "target": "Curitiba", "rel_type": "lives_in"},
                ],
            }
        )

        llm = FakeLLMProvider([response])
        result, _meta = run_async(extract_from_message("Vou pra Curitiba ver minha mãe", llm=llm))

        # Both the explicit and implicit facts are extracted
        assert len(result.facts) == 2
        assert any("lives in Curitiba" in f.fact for f in result.facts)
        assert len(result.relations) == 1


# ===========================================================================
# Tests: evidence_fact_id updated after mirror fact creation
# ===========================================================================


class TestMirrorFactEvidenceLinkage:
    """Tests that mirror fact ID is used as evidence_fact_id for the relation."""

    @pytest.mark.asyncio
    async def test_mirror_fact_is_flushed_to_session(self):
        """Mirror fact is added to session and flushed."""
        from arandu.write.upsert import _create_mirror_fact

        source = _make_resolved_entity("person:ana", "Ana")
        target = _make_resolved_entity("place:curitiba", "Curitiba")
        embeddings = FakeEmbeddingProvider()
        now = datetime.now(UTC)

        session = AsyncMock()

        fact = await _create_mirror_fact(
            session,
            "user-1",
            "event-1",
            source,
            target,
            "lives_in",
            now,
            embeddings,
        )

        # Verify session.add and session.flush were called
        session.add.assert_called_once_with(fact)
        session.flush.assert_awaited_once()
        assert fact.fact_text == "Ana lives in Curitiba"
        assert fact.source_context == "inferred_from_relation"
