"""Retrieval planner — deterministic query analysis for memory retrieval.

Analyzes the user query and produces a ``RetrievalPlan`` with:
- strategy: "multi_signal" (default) or "skip" (greetings)
- pattern_queries: SQL LIKE patterns for aggregation queries
- similarity_query: always the original query (no LLM reformulation)
- broad_query: flag for comprehensive queries

This module is **deterministic by design**: same input always produces
the same output. Entity extraction is NOT done here — the pipeline's
``resolve_query_entities`` handles that deterministically.

Anaphora resolution (pronoun → entity) is the **caller's responsibility**.
The caller has the conversation context and should resolve pronouns before
calling ``retrieve()``.

History: prior to v0.13.0 this module called an LLM for query
understanding, which introduced non-determinism (cloud LLM APIs are not
deterministic even at temperature=0). See bug 2d.
"""

from __future__ import annotations

import logging
import re
import time
from dataclasses import dataclass, field
from datetime import datetime

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from arandu.models import MemoryFact

logger = logging.getLogger(__name__)

SCHEMA_CACHE_TTL_SEC = 300  # 5 minutes

_schema_cache: dict[str, tuple[list[tuple[str, str]], float]] = {}

# ---------------------------------------------------------------------------
# Greeting detection — regex-based, no LLM needed
# ---------------------------------------------------------------------------
_GREETING_PATTERNS: list[re.Pattern[str]] = [
    # Portuguese
    re.compile(
        r"^(oi|olá|ola|eai|e aí|fala|salve|bom dia|boa tarde|boa noite"
        r"|tudo bem|tudo certo|como vai|beleza|fala aí|opa)[\s?!.,]*$",
        re.IGNORECASE,
    ),
    # English
    re.compile(
        r"^(hi|hello|hey|yo|sup|what'?s up|good morning|good afternoon"
        r"|good evening|how are you|howdy|greetings)[\s?!.,]*$",
        re.IGNORECASE,
    ),
]

# ---------------------------------------------------------------------------
# Broad query detection — regex-based
# ---------------------------------------------------------------------------
_BROAD_PATTERNS: list[re.Pattern[str]] = [
    # Portuguese
    re.compile(
        r"\b(tudo sobre|me conta tudo|todos?|todas?|tudo que sabe"
        r"|tudo o que sabe|resumo geral|visão geral|overview)\b",
        re.IGNORECASE,
    ),
    # English
    re.compile(
        r"\b(everything about|tell me everything|all about|overview"
        r"|full summary|all of my|everything you know)\b",
        re.IGNORECASE,
    ),
]

# ---------------------------------------------------------------------------
# Aggregation query detection — maps keywords to schema prefixes
# ---------------------------------------------------------------------------
_AGGREGATION_KEYWORDS: dict[str, list[str]] = {
    # prefix → trigger words
    "person": [
        "amigos",
        "amigo",
        "friends",
        "friend",
        "pessoas",
        "pessoa",
        "people",
        "person",
        "família",
        "familia",
        "family",
        "contatos",
        "contato",
        "contacts",
        "contact",
        "colegas",
        "colega",
        "colleagues",
    ],
    "organization": [
        "empresas",
        "empresa",
        "companies",
        "company",
        "organizações",
        "organização",
        "organizations",
        "organization",
    ],
    "project": [
        "projetos",
        "projeto",
        "projects",
        "project",
    ],
    "pet": [
        "pets",
        "pet",
        "animais",
        "animal",
        "bichos",
        "bicho",
    ],
    "place": [
        "lugares",
        "lugar",
        "places",
        "place",
        "locais",
        "local",
        "locations",
        "location",
    ],
}


@dataclass
class PatternQuery:
    """One pattern-based query: entity_key LIKE pattern, optional attribute filter.

    Attributes:
        entity_pattern: SQL LIKE pattern for entity_key matching.
        attribute_filter: Optional attribute key filter (always None currently).
    """

    entity_pattern: str
    attribute_filter: str | None = None


@dataclass
class RetrievalPlan:
    """Output of the retrieval planner.

    All signals (semantic, graph, keyword) run in parallel. The plan
    provides pattern queries for keyword signal, the original query for
    semantic signal, and budget/time-travel parameters.

    Attributes:
        strategy: "multi_signal" (default) or "skip".
        entities: Entity keys for graph signal (populated by pipeline,
            not by planner).
        pattern_queries: Pattern queries for keyword signal.
        similarity_query: The query for semantic search (always original).
        max_facts: Budget per signal.
        reason: Why this plan was chosen.
        latency_ms: Time spent planning.
        as_of_range: Optional time-travel window (start, end).
        broad_query: True for comprehensive queries.
    """

    strategy: str = "multi_signal"
    entities: list[str] = field(default_factory=list)
    pattern_queries: list[PatternQuery] = field(default_factory=list)
    similarity_query: str | None = None
    max_facts: int = 50
    reason: str = ""
    latency_ms: float = 0.0
    as_of_range: tuple[datetime, datetime] | None = None
    broad_query: bool = False


# ---------------------------------------------------------------------------
# Schema helpers (kept from prior version — still needed for pattern queries)
# ---------------------------------------------------------------------------
def _get_schema_prefixes(pairs: list[tuple[str, str]]) -> list[str]:
    """Return sorted list of group prefixes from entity_key pairs."""
    prefixes: set[str] = set()
    for entity_key, _ in pairs:
        if not entity_key:
            continue
        if ":" in entity_key:
            prefixes.add(entity_key.rsplit(":", 1)[0])
        else:
            prefixes.add(entity_key)
    return sorted(prefixes)


async def _get_user_schema(session: AsyncSession, agent_id: str) -> list[tuple[str, str]]:
    """Fetch distinct (entity_key, attribute_key) pairs for user. Cached 5 min per agent_id."""
    now = time.time()
    if agent_id in _schema_cache:
        pairs, expiry = _schema_cache[agent_id]
        if now < expiry:
            return pairs
    stmt = (
        select(MemoryFact.entity_key, MemoryFact.attribute_key)
        .where(
            MemoryFact.agent_id == agent_id,
            MemoryFact.valid_to.is_(None),
        )
        .distinct()
    )
    result = await session.execute(stmt)
    pairs = [(r[0], r[1]) for r in result.all() if r[0] and r[1]]
    _schema_cache[agent_id] = (pairs, now + SCHEMA_CACHE_TTL_SEC)
    return pairs


# ---------------------------------------------------------------------------
# Deterministic planner
# ---------------------------------------------------------------------------
def _is_greeting(query: str) -> bool:
    """Check if query is a greeting/casual message with no memory need."""
    return any(p.match(query.strip()) for p in _GREETING_PATTERNS)


def _is_broad(query: str) -> bool:
    """Check if query is a broad/comprehensive request."""
    return any(p.search(query) for p in _BROAD_PATTERNS)


def _detect_pattern_queries(
    query: str,
    schema_prefixes: list[str],
) -> list[PatternQuery]:
    """Detect aggregation queries and return pattern queries.

    Matches keywords in the query against known prefix categories. Only
    emits a pattern if the prefix actually exists in the user's schema.
    """
    query_lower = query.lower()
    patterns: list[PatternQuery] = []
    seen_prefixes: set[str] = set()

    for prefix, keywords in _AGGREGATION_KEYWORDS.items():
        if prefix in seen_prefixes:
            continue
        for kw in keywords:
            if re.search(rf"\b{re.escape(kw)}\b", query_lower):
                if prefix in schema_prefixes:
                    patterns.append(PatternQuery(entity_pattern=f"{prefix}:%"))
                    seen_prefixes.add(prefix)
                break

    return patterns


async def plan_retrieval(
    session: AsyncSession,
    agent_id: str,
    query_text: str,
) -> RetrievalPlan:
    """Deterministic query analysis — no LLM, same input = same output.

    Analyzes the query to decide:
    - Skip greetings (strategy="skip")
    - Detect aggregation patterns (pattern_queries)
    - Detect broad queries (broad_query flag)
    - Pass original query as similarity_query (no reformulation)

    Entity extraction is NOT done here — ``resolve_query_entities`` in the
    pipeline handles it deterministically.

    Args:
        session: Database session (for schema lookup).
        agent_id: User identifier.
        query_text: The user's query.

    Returns:
        RetrievalPlan — deterministic for the same (agent_id, query_text).
    """
    if not query_text or not query_text.strip():
        return RetrievalPlan(
            strategy="multi_signal",
            similarity_query=query_text,
            reason="empty_query",
            latency_ms=0.0,
        )

    start = time.perf_counter()

    # 1. Skip greetings
    if _is_greeting(query_text):
        return RetrievalPlan(
            strategy="skip",
            similarity_query=None,
            reason="greeting",
            latency_ms=(time.perf_counter() - start) * 1000,
        )

    # 2. Fetch schema for pattern detection
    try:
        pairs = await _get_user_schema(session, agent_id)
    except Exception as e:
        logger.warning("retrieval_planner: schema fetch failed: %s", e)
        pairs = []

    schema_prefixes = _get_schema_prefixes(pairs)

    # 3. Detect aggregation patterns
    pattern_queries = _detect_pattern_queries(query_text, schema_prefixes)

    # 4. Detect broad queries
    broad_query = _is_broad(query_text)

    latency_ms = (time.perf_counter() - start) * 1000

    reason = "deterministic"
    if pattern_queries:
        reason = "aggregation"
    elif broad_query:
        reason = "broad"

    return RetrievalPlan(
        strategy="multi_signal",
        entities=[],  # pipeline fills via resolve_query_entities
        pattern_queries=pattern_queries,
        similarity_query=query_text,  # no reformulation — deterministic
        max_facts=100 if broad_query else 50,
        reason=reason,
        latency_ms=latency_ms,
        broad_query=broad_query,
    )
