#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Briefing extension functions — P27/P28 neuroscience items.

Items housed here to keep briefing.py under 300 lines:
  - Item 112: Negative transfer detection
  - Item 120: Prospective memory reminder
"""
import logging
import re
from typing import Optional

log = logging.getLogger("pulse.brain_query.briefing_ext")

# ---------------------------------------------------------------------------
# Item 112: Negative transfer detection (Decision)
# ---------------------------------------------------------------------------
_NEGATIVE_TRANSFER_THRESHOLD = 0.7  # confidence above which to warn


# Domain keyword heuristics — same mapping used by contextual inhibition
_DOMAIN_HINTS: dict = {
    "security": {"security", "auth", "crypto", "token", "cert", "exploit"},
    "testing": {"test", "mock", "pytest", "assert", "coverage", "fixture"},
    "deployment": {"deploy", "docker", "k8s", "ci", "cd", "release", "prod"},
    "database": {"database", "sql", "sqlite", "query", "migration", "schema"},
    "api": {"api", "rest", "endpoint", "http", "request", "response", "route"},
    "brain": {"brain", "memory", "knowledge", "consolidation", "salience"},
}


def _infer_domain_from_text(text: str) -> Optional[str]:
    """Infer domain from a text fragment via keyword overlap."""
    try:
        words = set(re.findall(r"\b[a-z]{3,}\b", text.lower()))
        best: Optional[str] = None
        best_count = 0
        for domain, hints in _DOMAIN_HINTS.items():
            overlap = len(words & hints)
            if overlap > best_count:
                best_count = overlap
                best = domain
        return best if best_count >= 1 else None
    except Exception:
        return None


def detect_negative_transfer(query: str, hits: dict) -> list:
    """Return warning lines for high-confidence results from mismatched domains.

    Negative transfer = knowledge from domain X incorrectly applied to query
    that clearly belongs to domain Y.

    Args:
        query: the user's search query string.
        hits: the results dict (keyed by source name, lists of hit dicts).

    Returns:
        List of warning strings (may be empty).
    """
    warnings: list = []
    try:
        query_domain = _infer_domain_from_text(query)
        if not query_domain:
            return warnings

        all_hits = [h for src in hits.values() if isinstance(src, list) for h in src]
        seen_domains: set = set()

        for h in all_hits:
            try:
                conf = float(h.get("confidence", 0.0))
            except (TypeError, ValueError):
                conf = 0.0
            if conf <= _NEGATIVE_TRANSFER_THRESHOLD:
                continue

            item_domain = (h.get("domain") or "").lower().strip()
            if not item_domain:
                # Try to infer from content
                text = (h.get("text") or h.get("title") or h.get("content") or "")
                item_domain = _infer_domain_from_text(text) or ""

            if item_domain and item_domain != query_domain and item_domain not in seen_domains:
                seen_domains.add(item_domain)
                text_preview = (h.get("text") or h.get("title") or "")[:60].strip()
                warnings.append(
                    f"  !! NEGATIVE TRANSFER: High-conf item from domain '{item_domain}' "
                    f"applied to '{query_domain}' query — '{text_preview}'"
                )
    except Exception as e:
        log.debug("Negative transfer detection failed: %s", e)
    return warnings


# ---------------------------------------------------------------------------
# Item 120: Prospective memory reminder (Attention)
# ---------------------------------------------------------------------------

def check_prospective_memory(query: str) -> list:
    """Surface intent items whose trigger_condition matches the current query.

    Prospective memory = remembering to do something when a specific cue
    appears. If the brain has intents tagged with trigger conditions, and the
    current query matches that trigger, surface a REMINDER.

    Returns:
        List of "!! REMINDER: ..." strings (may be empty).
    """
    reminders: list = []
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        rows = conn.execute(
            "SELECT title, content, trigger_condition FROM intents "
            "WHERE trigger_condition IS NOT NULL AND trigger_condition != '' "
            "LIMIT 50"
        ).fetchall()
        q_lower = query.lower()
        for row in rows:
            try:
                trigger = (row["trigger_condition"] if hasattr(row, "__getitem__") else row[2] or "")
                title = (row["title"] if hasattr(row, "__getitem__") else row[0] or "")
                content = (row["content"] if hasattr(row, "__getitem__") else row[1] or "")

                if not trigger:
                    continue
                # Match: trigger words appear in query
                trigger_words = set(re.findall(r"\b[a-z]{3,}\b", trigger.lower()))
                q_words = set(re.findall(r"\b[a-z]{3,}\b", q_lower))
                if len(trigger_words & q_words) >= 2 or (
                    len(trigger_words) == 1 and trigger_words & q_words
                ):
                    label = title or content[:60]
                    reminders.append(f"!! REMINDER: {label[:100].strip()}")
            except Exception:
                continue
    except Exception as e:
        log.debug("Prospective memory check failed: %s", e)
    return reminders


# ---------------------------------------------------------------------------
# Item 141: Inhibitory control — suppress impulsive responses (Decision)
# ---------------------------------------------------------------------------
_LOW_CONFIDENCE_THRESHOLD = 0.4  # avg confidence below this triggers warning


def check_low_confidence_warning(avg_conf: float, all_hits: list) -> list:
    """Return warning lines when brain has low overall confidence.

    Inhibitory control: prevent impulsive action on shaky/uncertain knowledge.
    Fires when avg_conf < 0.4 AND there are at least some results (not complete
    ignorance — that is handled separately).

    Args:
        avg_conf: average confidence across all results above ignition threshold.
        all_hits: flat list of all result items that passed ignition gate.

    Returns:
        List containing a prominent warning string, or empty list.
    """
    try:
        if not all_hits:
            return []
        if avg_conf < _LOW_CONFIDENCE_THRESHOLD:
            return [
                f"!! LOW CONFIDENCE: Brain avg confidence {avg_conf:.2f} — "
                "Consider verifying before acting"
            ]
    except Exception:
        pass  # fail open
    return []


# ---------------------------------------------------------------------------
# Item 148: Predictive coding — expected vs actual results (Metacognition)
# ---------------------------------------------------------------------------
_FAMILIARITY_CACHE: dict = {}  # {domain: avg_item_count} populated lazily
_KNOWLEDGE_GAP_RATIO = 0.5    # actual < 50% of expected = GAP
_KNOWLEDGE_SURGE_RATIO = 2.0  # actual > 200% of expected = SURGE


def _expected_results_for_domain(domain: str) -> float:
    """Estimate expected result count from domain familiarity in brain.

    Uses average item count per domain from lessons/failures tables.
    Returns 0.0 if domain is unknown (no cached data).
    Fails open.
    """
    if domain in _FAMILIARITY_CACHE:
        return _FAMILIARITY_CACHE[domain]
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        total = 0
        for tbl in ("lessons", "failures"):
            try:
                row = conn.execute(
                    f"SELECT COUNT(*) FROM {tbl} WHERE domain = ? AND validity = 'valid'",
                    (domain,),
                ).fetchone()
                total += int(row[0] or 0)
            except Exception:
                pass
        _FAMILIARITY_CACHE[domain] = float(total)
        return float(total)
    except Exception:
        return 0.0


def check_predictive_coding(query: str, actual_count: int,
                             inferred_domain: str = "") -> list:
    """Compare expected vs actual result count; flag KNOWLEDGE_GAP or SURGE.

    Item 148: Predictive coding metacognition.
    - If actual << expected (< 50%): !! KNOWLEDGE GAP
    - If actual >> expected (> 200%): !! KNOWLEDGE SURGE
    Returns list of warning strings (may be empty).
    Fails open.
    """
    warnings: list = []
    try:
        domain = inferred_domain.strip() if inferred_domain else ""
        if not domain:
            # Try to infer from query
            domain = _infer_domain_from_text(query) or ""
        if not domain:
            return warnings
        expected = _expected_results_for_domain(domain)
        if expected < 2:
            return warnings  # Not enough data to predict
        ratio = actual_count / expected if expected > 0 else 1.0
        if ratio < _KNOWLEDGE_GAP_RATIO:
            warnings.append(
                f"!! KNOWLEDGE GAP: Expected ~{expected:.0f} results for domain '{domain}' "
                f"but got {actual_count} (ratio={ratio:.2f})"
            )
        elif ratio > _KNOWLEDGE_SURGE_RATIO:
            warnings.append(
                f"!! KNOWLEDGE SURGE: Expected ~{expected:.0f} results for domain '{domain}' "
                f"but got {actual_count} (ratio={ratio:.2f})"
            )
    except Exception:
        pass  # fail open
    return warnings


# ---------------------------------------------------------------------------
# Item 153: Response inhibition gate (Decision)
# ---------------------------------------------------------------------------
_INHIBITION_FAILURE_WINDOW = 3   # look at last N queries
_QUERY_FAILURE_LOG: list = []    # [(query_text, failed: bool)]

INHIBITION_MESSAGE = (
    "!! RESPONSE INHIBITION: Recent failures detected. Recommend pause and review."
)


def _record_query_outcome(query: str, failed: bool) -> None:
    """Record whether a query led to a task failure.

    Call this after task completion to update the failure log used by the
    response inhibition gate.
    Fails open silently.
    """
    try:
        _QUERY_FAILURE_LOG.append((query[:100], bool(failed)))
        # Keep only the last window
        while len(_QUERY_FAILURE_LOG) > _INHIBITION_FAILURE_WINDOW * 2:
            _QUERY_FAILURE_LOG.pop(0)
    except Exception:
        pass


def check_response_inhibition() -> list:
    """Return inhibition message if last 3 queries all resulted in failures.

    Returns:
        List containing INHIBITION_MESSAGE string, or empty list.
    Fails open (returns []) on any error.
    """
    try:
        recent = _QUERY_FAILURE_LOG[-_INHIBITION_FAILURE_WINDOW:]
        if len(recent) < _INHIBITION_FAILURE_WINDOW:
            return []
        if all(failed for _, failed in recent):
            return [INHIBITION_MESSAGE]
    except Exception:
        pass  # fail open
    return []
record_query_outcome = _record_query_outcome
