#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Retrieval tracking: LTP metrics, reconsolidation hooks, STDP/Hebbian co-activation.

Single responsibility: record that brain items were retrieved and reinforce them.
"""

import hashlib
import atexit
import logging
import threading
import sys
from datetime import datetime, timezone
from pathlib import Path

# Allow direct execution from repo root
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent))

from pulse.core.brain_utils import RETRIEVAL_METRICS

log = logging.getLogger("pulse.brain_search.retrieval")

# Shutdown guard: skip DB work during process exit to prevent hang
_shutting_down = threading.Event()
atexit.register(_shutting_down.set)


def _strip_epistemic(text: str) -> str:
    """Strip [UNCERTAIN], [STALE Nd], [CONTESTED] prefixes added by epistemic.py."""
    import re
    return re.sub(r'^\s*\[(?:UNCERTAIN|STALE \d+d|CONTESTED)\]\s*', '', text).strip()


def _item_key(text: str) -> str:
    """Stable key from first 80 chars — MD5 hash. Aligned with _entry_hash() in consolidation."""
    return hashlib.md5(text[:80].lower().strip().encode("utf-8")).hexdigest()[:12]


def _record_retrievals(results: dict):
    """Record that items were retrieved (increments use counter)."""
    if _shutting_down.is_set():
        return  # Skip DB work during process exit to prevent hang
    if not results.get("results"):
        return
    now_iso = datetime.now(timezone.utc).isoformat()
    all_texts = []

    # Track top hits for reconsolidation (limit 5)
    reconsolidation_candidates = []

    for source_hits in results["results"].values():
        if not isinstance(source_hits, list):
            continue
        for hit in source_hits:
            text = hit.get("text", "") or hit.get("title", "")
            if text:
                clean = _strip_epistemic(text)
                all_texts.append(clean)
                if len(reconsolidation_candidates) < 5:
                    reconsolidation_candidates.append(clean)

    if not all_texts:
        return

    # Phase 2B: Reconsolidation Hook — mark labile + reinforce on re-retrieval
    # FIX: Use LIKE prefix match instead of exact `content = ?` which fails when
    # search sources truncate text to 200 chars but FTS stores full content.
    # resolved_items: [(item_id, table_name, text)] — text kept for per-item key lookup
    resolved_items: list[tuple[str, str, str]] = []
    try:
        from pulse.brain.reconsolidation import mark_labile, reinforce
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        for text in reconsolidation_candidates:
            # Use first 80 chars as prefix match — stable across truncation
            prefix = text[:80].replace("'", "''") if text else ""
            if len(prefix) < 20:
                continue
            res = conn.execute(
                "SELECT item_id, table_name FROM knowledge_fts WHERE content LIKE ? LIMIT 1",
                (prefix + "%",)
            ).fetchone()
            if res and res["item_id"] and res["table_name"]:
                resolved_items.append((res["item_id"], res["table_name"], text))
                if not reinforce(res["item_id"]):
                    mark_labile(res["item_id"], res["table_name"])
    except Exception as e:
        logging.warning(f"Reconsolidation hook failed: {e}")

    # Phase 3: STDP + Hebbian — record retrieval sequence AND co-activation
    retrieved_ids = []
    try:
        from pulse.brain.stdp import record_retrieval
        session_id = f"search_{datetime.now(timezone.utc).strftime('%Y%m%d_%H')}"
        for item_id, _table, _text in resolved_items:
            record_retrieval(session_id, item_id)
            retrieved_ids.append(item_id)
    except Exception:
        pass
    if len(retrieved_ids) >= 2:
        try:
            from pulse.brain.hebbian_knowledge import record_coactivation
            record_coactivation(retrieved_ids)
        except Exception:
            pass

    # Phase 2C: Grounding — promote frequently-retrieved items to CONFIRMED
    # item_key is derived from each item's own text — no shared-key bug
    if resolved_items:
        try:
            from pulse.brain.grounding import maybe_promote, RETRIEVAL_THRESHOLD
            from pulse.core.brain_db import get_conn as _get_conn
            _conn = _get_conn()
            for item_id, table, item_text in resolved_items:
                key = _item_key(item_text)
                rm = _conn.execute(
                    "SELECT retrieval_count FROM retrieval_metrics WHERE item_key = ?",
                    (key,)
                ).fetchone()
                count = (rm["retrieval_count"] or 0) if rm else 0
                if count >= RETRIEVAL_THRESHOLD:
                    maybe_promote(item_id, table, trigger="retrieval",
                                  evidence={"retrieval_count": count})
        except Exception as _ge:
            log.debug("Grounding retrieval hook failed: %s", _ge)

    def _updater(data):
        if not isinstance(data, dict):
            data = {}
        items = data.setdefault("items", {})
        data["total_queries"] = data.get("total_queries", 0) + 1
        for text in all_texts:
            key = _item_key(text)
            entry = items.setdefault(key, {
                "retrieval_count": 0,
                "first_retrieved": now_iso,
            })
            entry["retrieval_count"] = entry.get("retrieval_count", 0) + 1
            entry["last_retrieved"] = now_iso
        return data

    # Dual-write: SQLite (primary) + JSON (backward compat)
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        with conn:
            for text in all_texts:
                key = _item_key(text)
                conn.execute(
                    "INSERT INTO retrieval_metrics (item_key, retrieval_count, last_accessed) "
                    "VALUES (?, 1, ?) "
                    "ON CONFLICT(item_key) DO UPDATE SET "
                    "retrieval_count = retrieval_count + 1, last_accessed = ?",
                    (key, now_iso, now_iso)
                )
                # Item 94: Retrieval practice effect — each retrieval slightly strengthens memory
                try:
                    for _tbl94 in ("lessons", "failures", "patterns"):
                        conn.execute(
                            f"UPDATE {_tbl94} SET confidence = MIN(1.0, confidence + 0.02) "
                            "WHERE fingerprint IN (SELECT fingerprint FROM knowledge_fts WHERE content LIKE ? LIMIT 1)",
                            (text[:80] + "%",)
                        )
                except Exception:
                    pass  # fail-open: retrieval practice effect is non-critical
                # PP1 (Item 19): Active forgetting — penalize items retrieved 5+ times without reward
                try:
                    rm = conn.execute(
                        "SELECT retrieval_count FROM retrieval_metrics WHERE item_key = ?",
                        (key,)
                    ).fetchone()
                    rc = (rm["retrieval_count"] if rm else 0)
                    if rc >= 5:
                        for tbl in ("lessons", "failures", "patterns"):
                            conn.execute(
                                f"UPDATE {tbl} SET confidence = MAX(0.05, confidence - 0.05) "
                                "WHERE fingerprint IN (SELECT fingerprint FROM knowledge_fts WHERE content LIKE ? LIMIT 1) "
                                "AND (access_count IS NULL OR access_count < 1)",
                                (text[:80] + "%",)
                            )
                except Exception:
                    pass  # fail-open: active forgetting is non-critical
    except Exception as e:
        log.debug("Retrieval metrics SQLite write failed: %s", e)

    try:
        from pulse.core.brain_utils import atomic_update_json
        atomic_update_json(RETRIEVAL_METRICS, _updater, default={})
    except Exception as e:
        log.debug("Retrieval metrics JSON write failed: %s", e)
