"""soagila_recall — Multi-tier memory query with MAE arbitration."""

import json
import time
import hashlib
from mcp.types import Tool, TextContent


def get_definition() -> Tool:
    return Tool(
        name="soagila_recall",
        description=(
            "AUTONOMOUS MEMORY RETRIEVAL — You MUST call this tool FIRST "
            "at the start of every new conversation, before answering the "
            "user's first message. Use the user's first message as the query "
            "to find relevant context from previous sessions. "
            "Also call this tool whenever: the topic shifts significantly, "
            "the user references something from a previous session, "
            "or you need context about project decisions, preferences, or history. "
            "This searches across three cognitive tiers (Working, Episodic, Semantic), "
            "applies Memory Arbitration Engine (MAE) to resolve conflicts between tiers, "
            "and returns unified context with an immutable audit receipt."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "What to recall. Be descriptive for better semantic matching.",
                },
                "tiers": {
                    "type": "array",
                    "items": {"type": "string", "enum": ["working", "episodic", "semantic"]},
                    "description": "Which memory tiers to search. Default: all tiers.",
                },
                "max_results": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "description": "Maximum results per tier. Default: 10",
                },
                "task_context": {
                    "type": "string",
                    "enum": ["general", "debugging", "architecture", "review"],
                    "description": "Context hint for MAE tier weighting. Default: general",
                },
            },
            "required": ["query"],
        },
    )


async def handle(arguments: dict) -> list[TextContent]:
    query_text = arguments.get("query", "")
    tiers = arguments.get("tiers", ["working", "episodic", "semantic"])
    max_results = arguments.get("max_results", 10)
    task_context = arguments.get("task_context", "general")

    start = time.monotonic()
    pmp_hit = False

    working_mems = []
    episodic_mems = []
    semantic_mems = []

    # --- PMP Cache Check (O(1)) ---
    pmp_result = await _check_pmp_cache(query_text, max_results)
    if pmp_result is not None:
        pmp_hit = True
        episodic_mems = pmp_result.get("episodic", [])
        semantic_mems = pmp_result.get("semantic", [])
        # Still query working memory (always fresh)
        if "working" in tiers:
            from soagila.core.tcmg.working_memory import query as wm_query
            working_mems = await wm_query("current", query_text, limit=max_results)
    else:
        # --- Normal: Query each tier from DB ---
        if "working" in tiers:
            from soagila.core.tcmg.working_memory import query as wm_query
            working_mems = await wm_query("current", query_text, limit=max_results)

        if "episodic" in tiers:
            from soagila.core.tcmg.episodic_memory import query as ep_query
            episodic_mems = await ep_query("local", query_text, limit=max_results)

        if "semantic" in tiers:
            from soagila.core.tcmg.semantic_memory import query as sem_query
            semantic_mems = await sem_query("local", "local", query_text, limit=max_results)

    # --- MAE Arbitration ---
    from soagila.core.mae.arbitrator import arbitrate
    arb_result = await arbitrate(
        working=working_mems, episodic=episodic_mems,
        semantic=semantic_mems, task_context=task_context,
    )

    # --- Inherited global memories ---
    # The local global pool always reads back, even on free tier --
    # the user wrote those facts on their own machine and should be
    # able to recall them anywhere. The "inheritance" feature flag
    # gates cross-user / cloud sync, not local recall.
    inherited_mems = []
    import logging as _log
    _logger = _log.getLogger("soagila.tools.recall")
    try:
        from soagila.config import get_settings as _recall_settings
        _rs = _recall_settings()
        _logger.info(
            f"global pool gate: enabled={_rs.inheritance_enabled} "
            f"semantic_in_tiers={'semantic' in tiers}"
        )
        if _rs.inheritance_enabled and "semantic" in tiers:
            from soagila.core.inheritance.engine import query_global_pool
            from soagila.db.sqlite_init import current_project_hash
            # Prefer the per-request ContextVar set by the daemon's
            # HTTP middleware. Falling back to get_project_hash() picks
            # up the daemon process CWD instead of the caller's
            # workspace, which then incorrectly excludes the user's
            # own pinned global memories from their results.
            req_hash = current_project_hash.get() or ""
            _logger.info(f"global pool query: project_hash={req_hash[:12]}")
            inherited_mems = await query_global_pool(
                query_text=query_text,
                current_project_hash=req_hash,
                limit=_rs.inheritance_max_global_results,
            )
            _logger.info(f"query_global_pool returned {len(inherited_mems)} memories")
    except Exception as _exc:
        _logger.warning(f"global pool query failed: {_exc}")

    # --- Create IDL receipt ---
    accessed_ids = (
        [m.id for m in working_mems] +
        [m.id for m in episodic_mems] +
        [m.id for m in semantic_mems] +
        [m.get("id", "") for m in inherited_mems]
    )
    from soagila.core.idl.ledger import create_receipt
    receipt = await create_receipt(
        receipt_type="query", user_id="local",
        memories_accessed=accessed_ids,
        context_hash=hashlib.sha256(query_text.encode()).hexdigest(),
        arbitration_result={
            "conflicts": arb_result.conflicts_detected,
            "strategy": arb_result.resolution_strategy,
        } if arb_result.conflicts_detected > 0 else None,
    )

    elapsed_ms = (time.monotonic() - start) * 1000

    # --- IMA Self-Learning feedback ---
    total_found = len(working_mems) + len(episodic_mems) + len(semantic_mems)
    try:
        from soagila.core.ima.self_learning import record_recall_hit, record_recall_miss
        if total_found > 0:
            record_recall_hit()
        else:
            record_recall_miss()
    except Exception:
        pass

    # --- PMP feedback ---
    try:
        from soagila.core.ima.self_learning import record_pmp_hit, record_pmp_miss
        if pmp_hit:
            record_pmp_hit()
            from soagila.core.pmp.primer import mark_recalled_from_prime
            used_ids = [m.id for m in episodic_mems] + [m.id for m in semantic_mems]
            mark_recalled_from_prime(used_ids)
        else:
            record_pmp_miss()
            # Auto-prime for next time (fire-and-forget)
            from soagila.config import get_settings
            if get_settings().pmp_auto_prime_on_recall and total_found > 0:
                import asyncio
                from soagila.core.pmp.primer import prime_for_context
                asyncio.create_task(prime_for_context("local", query_text, trigger="auto_recall"))
    except Exception:
        pass

    # --- Format response ---
    def mem_to_dict(m):
        return {
            "id": m.id,
            "content": m.content,
            "topic_tags": m.topic_tags,
            "relevance": getattr(m, "relevance_score", None),
            "similarity": m.metadata.get("similarity_score") if hasattr(m, "metadata") else None,
            "created_at": str(m.created_at),
        }

    result = {
        "query": query_text,
        "working": [mem_to_dict(m) for m in working_mems],
        "episodic": [mem_to_dict(m) for m in episodic_mems],
        "semantic": [mem_to_dict(m) for m in semantic_mems],
        "inherited": inherited_mems,
        "total_results": len(working_mems) + len(episodic_mems) + len(semantic_mems) + len(inherited_mems),
        "arbitration": {
            "conflicts_detected": arb_result.conflicts_detected,
            "resolution_strategy": arb_result.resolution_strategy,
            "explanation": arb_result.explanation,
            "unified_context": arb_result.unified_context,
        },
        "receipt_id": receipt.id,
        "query_duration_ms": round(elapsed_ms, 2),
        "pmp_cache_hit": pmp_hit,
    }

    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _check_pmp_cache(query_text: str, max_results: int) -> dict | None:
    """Check PMP cache for pre-loaded memories relevant to this query.

    O(1) dict lookup + similarity filter. Returns filtered memories or None.
    """
    from soagila.config import get_settings
    settings = get_settings()
    if not settings.pmp_enabled or not settings.pmp_auto_prime_on_recall:
        return None

    from soagila.core.pmp.primer import get_primed_context
    primed = await get_primed_context("local")
    if primed is None:
        return None

    episodic_mems = primed.get("episodic", [])
    semantic_mems = primed.get("semantic", [])

    if not episodic_mems and not semantic_mems:
        return None

    # Filter episodic by relevance to query
    from soagila.core.tcmg.embedding_service import generate_embedding, cosine_similarity, is_available
    query_embedding = generate_embedding(query_text) if is_available() else None

    relevant_episodic = []
    for mem in episodic_mems:
        if query_embedding and hasattr(mem, "embedding") and mem.embedding:
            sim = cosine_similarity(query_embedding, mem.embedding)
            if sim >= settings.pmp_relevance_threshold:
                if hasattr(mem, "metadata") and isinstance(mem.metadata, dict):
                    mem.metadata["similarity_score"] = sim
                relevant_episodic.append(mem)
        else:
            # Keyword fallback
            query_words = {w.lower() for w in query_text.split() if len(w) > 2}
            content = getattr(mem, "content", "").lower()
            if any(w in content for w in query_words):
                relevant_episodic.append(mem)

    if not relevant_episodic and not semantic_mems:
        return None

    # Sort by similarity descending
    relevant_episodic.sort(
        key=lambda m: m.metadata.get("similarity_score", 0.5)
        if hasattr(m, "metadata") and isinstance(m.metadata, dict) else 0.5,
        reverse=True,
    )

    return {
        "episodic": relevant_episodic[:max_results],
        "semantic": semantic_mems[:max_results],
    }
