"""Tests for deterministic retrieval planner — no LLM, same input = same output.

Covers:
- Greeting detection (skip strategy)
- Aggregation pattern detection
- Broad query detection
- Determinism guarantee (same input → same plan)
- Empty query handling
"""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock

from conftest import run_async

from arandu.read.retrieval_agent import (
    _detect_pattern_queries,
    _is_broad,
    _is_greeting,
    plan_retrieval,
)

# ===========================================================================
# Greeting detection
# ===========================================================================


class TestGreetingDetection:
    def test_portuguese_greetings(self) -> None:
        assert _is_greeting("oi") is True
        assert _is_greeting("Olá") is True
        assert _is_greeting("bom dia") is True
        assert _is_greeting("boa tarde!") is True
        assert _is_greeting("e aí") is True
        assert _is_greeting("tudo bem?") is True
        assert _is_greeting("fala") is True
        assert _is_greeting("salve") is True

    def test_english_greetings(self) -> None:
        assert _is_greeting("hi") is True
        assert _is_greeting("Hello") is True
        assert _is_greeting("hey") is True
        assert _is_greeting("good morning") is True
        assert _is_greeting("how are you") is True

    def test_non_greetings(self) -> None:
        assert _is_greeting("onde eu trabalho?") is False
        assert _is_greeting("oi, me conta sobre a Ana") is False
        assert _is_greeting("hello, what do you know about me?") is False
        assert _is_greeting("bom dia, quero saber meus projetos") is False


# ===========================================================================
# Broad query detection
# ===========================================================================


class TestBroadDetection:
    def test_portuguese_broad(self) -> None:
        assert _is_broad("me conta tudo sobre mim") is True
        assert _is_broad("tudo que sabe sobre a Ana") is True
        assert _is_broad("quero um resumo geral") is True
        assert _is_broad("visão geral da minha vida") is True

    def test_english_broad(self) -> None:
        assert _is_broad("tell me everything about me") is True
        assert _is_broad("everything you know about Pedro") is True
        assert _is_broad("give me a full summary") is True

    def test_non_broad(self) -> None:
        assert _is_broad("onde eu trabalho?") is False
        assert _is_broad("qual o nome da minha esposa?") is False


# ===========================================================================
# Pattern query detection
# ===========================================================================


class TestPatternDetection:
    def test_friends_query_matches_person_prefix(self) -> None:
        patterns = _detect_pattern_queries("quem são meus amigos?", ["person", "organization"])
        assert len(patterns) == 1
        assert patterns[0].entity_pattern == "person:%"

    def test_companies_query_matches_org_prefix(self) -> None:
        patterns = _detect_pattern_queries("minhas empresas", ["person", "organization"])
        assert len(patterns) == 1
        assert patterns[0].entity_pattern == "organization:%"

    def test_no_match_if_prefix_not_in_schema(self) -> None:
        patterns = _detect_pattern_queries("meus projetos", ["person", "organization"])
        assert len(patterns) == 0

    def test_match_when_prefix_exists(self) -> None:
        patterns = _detect_pattern_queries("meus projetos", ["person", "project"])
        assert len(patterns) == 1
        assert patterns[0].entity_pattern == "project:%"

    def test_no_pattern_for_specific_query(self) -> None:
        patterns = _detect_pattern_queries("onde a Ana mora?", ["person"])
        assert len(patterns) == 0

    def test_english_keywords(self) -> None:
        patterns = _detect_pattern_queries("who are my friends?", ["person"])
        assert len(patterns) == 1
        assert patterns[0].entity_pattern == "person:%"


# ===========================================================================
# plan_retrieval — integration
# ===========================================================================


def _mock_session_with_schema(pairs: list[tuple[str, str]]) -> AsyncMock:
    """Create a mock AsyncSession that returns schema pairs from execute()."""
    mock_result = MagicMock()
    mock_result.all.return_value = pairs

    session = AsyncMock()
    session.execute = AsyncMock(return_value=mock_result)
    return session


class TestPlanRetrieval:
    def test_empty_query(self) -> None:
        session = AsyncMock()
        plan = run_async(plan_retrieval(session, "user_1", ""))
        assert plan.strategy == "multi_signal"
        assert plan.reason == "empty_query"

    def test_greeting_skipped(self) -> None:
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = _mock_session_with_schema([])
        plan = run_async(plan_retrieval(session, "user_1", "oi"))
        assert plan.strategy == "skip"
        assert plan.reason == "greeting"

    def test_specific_query_uses_multi_signal(self) -> None:
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = _mock_session_with_schema([("person:ana", "location")])
        plan = run_async(plan_retrieval(session, "user_1", "onde a Ana mora?"))
        assert plan.strategy == "multi_signal"
        assert plan.similarity_query == "onde a Ana mora?"

    def test_aggregation_query_has_pattern(self) -> None:
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = _mock_session_with_schema([("person:ana", "location"), ("person:pedro", "profession")])
        plan = run_async(plan_retrieval(session, "user_1", "quem são meus amigos?"))
        assert len(plan.pattern_queries) == 1
        assert plan.pattern_queries[0].entity_pattern == "person:%"
        assert plan.reason == "aggregation"

    def test_broad_query_detected(self) -> None:
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = _mock_session_with_schema([])
        plan = run_async(plan_retrieval(session, "user_1", "me conta tudo sobre mim"))
        assert plan.broad_query is True
        assert plan.max_facts == 100

    def test_similarity_query_equals_original(self) -> None:
        """Core determinism guarantee: similarity_query is ALWAYS the original query."""
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = _mock_session_with_schema([("person:pedro", "profession")])
        query = "o que o Pedro faz da vida?"
        plan = run_async(plan_retrieval(session, "user_1", query))
        assert plan.similarity_query == query

    def test_entities_always_empty(self) -> None:
        """Planner does NOT extract entities — pipeline does that separately."""
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = _mock_session_with_schema([("person:pedro", "profession")])
        plan = run_async(plan_retrieval(session, "user_1", "o que o Pedro faz?"))
        assert plan.entities == []

    def test_determinism_same_input_same_output(self) -> None:
        """Same input always produces exactly the same plan — no LLM variance."""
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = _mock_session_with_schema([("person:ana", "location")])
        query = "onde a Ana mora?"

        plans = [run_async(plan_retrieval(session, "user_1", query)) for _ in range(20)]

        # All plans must be identical
        for p in plans[1:]:
            assert p.strategy == plans[0].strategy
            assert p.similarity_query == plans[0].similarity_query
            assert p.entities == plans[0].entities
            assert p.pattern_queries == plans[0].pattern_queries
            assert p.broad_query == plans[0].broad_query
            assert p.reason == plans[0].reason

    def test_schema_fetch_failure_graceful(self) -> None:
        """Schema fetch failure doesn't crash — returns basic plan."""
        from arandu.read.retrieval_agent import _schema_cache

        _schema_cache.clear()
        session = AsyncMock()
        session.execute = AsyncMock(side_effect=RuntimeError("DB down"))
        plan = run_async(plan_retrieval(session, "user_1", "onde eu trabalho?"))
        assert plan.strategy == "multi_signal"
        assert plan.similarity_query == "onde eu trabalho?"
