"""End-to-end test — write → retrieve proving the SDK works complete.

Uses mocked DB session and Protocol-compliant fake providers.
Simulates the full pipeline: extract facts from a message, then retrieve them.
"""

import json
import uuid
from dataclasses import dataclass
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock

from conftest import FakeEmbeddingProvider, FakeLLMProvider, run_async

# ---------------------------------------------------------------------------
# Fake MemoryFact for retrieval results
# ---------------------------------------------------------------------------


@dataclass
class FakeMemoryFact:
    id: uuid.UUID = None
    user_id: str = "user_123"
    entity_type: str = "person"
    entity_key: str = "user:self"
    entity_name: str = "user"
    attribute_key: str = None
    value_text: str = None
    fact_text: str = ""
    confidence: float = 0.9
    importance: float = 0.5
    valid_from: datetime = None
    valid_to: datetime = None
    embedding: list = None
    embedding_vec: list = None

    def __post_init__(self):
        if self.id is None:
            self.id = uuid.uuid4()
        if self.valid_from is None:
            self.valid_from = datetime.now(UTC)


# ===========================================================================
# Test: end-to-end write → retrieve
# ===========================================================================


class TestEndToEnd:
    def test_write_then_retrieve(self):
        """Simulate write pipeline storing facts, then retrieve finding them.

        This test proves:
        1. write() extracts facts from a message
        2. retrieve() can find relevant facts via semantic + keyword search
        3. The SDK works end-to-end with fake providers
        """
        from arandu.config import MemoryConfig
        from arandu.read.pipeline import run_read_pipeline
        from arandu.write.pipeline import run_write_pipeline

        # -- Setup: extraction response for the write pipeline --
        extraction_response = json.dumps(
            {
                "entities": [
                    {"name": "user", "type": "person"},
                    {"name": "São Paulo", "type": "place"},
                ],
                "facts": [
                    {"subject": "user", "fact": "Lives in São Paulo", "confidence": 0.95},
                ],
                "relations": [],
            }
        )

        # Reranker response for the retrieve pipeline
        reranker_response = json.dumps({"scores": [0.9]})

        llm = FakeLLMProvider([extraction_response, reranker_response])
        emb = FakeEmbeddingProvider()
        config = MemoryConfig(enable_reranker=True, topk_facts=5, extraction_mode="single_pass")

        # -- Mock session for write --
        mock_session = AsyncMock()
        mock_session.add = MagicMock()
        mock_session.flush = AsyncMock()
        mock_session.begin_nested = MagicMock()
        mock_session.begin_nested.return_value.__aenter__ = AsyncMock()
        mock_session.begin_nested.return_value.__aexit__ = AsyncMock(return_value=False)

        # Mock execute for write pipeline queries (entity cache, alias cache, etc.)
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute = AsyncMock(return_value=mock_result)

        # -- Step 1: Write --
        write_result = run_async(
            run_write_pipeline(
                session=mock_session,
                user_id="user_123",
                message="Eu moro em São Paulo",
                llm=llm,
                embeddings=emb,
                config=config,
            )
        )

        assert write_result["event_id"]
        assert len(write_result["facts_added"]) >= 1

        # -- Step 2: Retrieve (with a fake fact in the DB) --
        # Simulate the fact that was written being returned by pgvector search
        stored_fact = FakeMemoryFact(
            fact_text="Lives in São Paulo",
            entity_name="user",
            confidence=0.95,
        )

        # Mock semantic search result (returns fact + similarity)
        semantic_result = MagicMock()
        semantic_result.all.return_value = [(stored_fact, 0.85)]

        # Mock keyword search result (returns fact via scalars)
        keyword_result = MagicMock()
        keyword_result.scalars.return_value.all.return_value = [stored_fact]

        call_count = 0

        async def mock_execute(stmt, *args, **kwargs):
            nonlocal call_count
            call_count += 1
            # First call: semantic search, second: keyword search
            if call_count == 1:
                return semantic_result
            return keyword_result

        mock_read_session = AsyncMock()
        mock_read_session.execute = mock_execute

        read_result = run_async(
            run_read_pipeline(
                session=mock_read_session,
                user_id="user_123",
                query="onde o usuário mora?",
                llm=llm,
                embeddings=emb,
                config=config,
            )
        )

        assert read_result.total_candidates >= 1
        assert len(read_result.facts) >= 1
        assert "São Paulo" in read_result.facts[0].value
        assert read_result.context != ""
        assert "São Paulo" in read_result.context
        assert read_result.duration_ms >= 0

    def test_retrieve_no_results(self):
        """Retrieve with no matching facts returns empty result."""
        from arandu.config import MemoryConfig
        from arandu.read.pipeline import run_read_pipeline

        llm = FakeLLMProvider()
        emb = FakeEmbeddingProvider()
        config = MemoryConfig(enable_reranker=False)

        # Mock session returning empty results
        mock_session = AsyncMock()
        mock_result = MagicMock()
        mock_result.all.return_value = []
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute = AsyncMock(return_value=mock_result)

        result = run_async(
            run_read_pipeline(
                session=mock_session,
                user_id="user_123",
                query="something completely unrelated",
                llm=llm,
                embeddings=emb,
                config=config,
            )
        )

        assert result.facts == []
        assert result.context == ""
        assert result.total_candidates == 0
