# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Brain data loaders for agent boot sequence."""
from __future__ import annotations
import json

from pulse.core.brain_utils import (
    HIPPOCAMPUS_DIR, STATE_DIR, ANTI_PATTERNS_DIR,
    LESSONS_JSONL, FAILS_JSONL, PATTERNS_JSONL,
    load_json_dict, load_json, _extract_jsonl_titles as extract_jsonl_titles,
)

from pulse.core.paths import get_state_dir
from pulse.brain.brain_search import brain_search

# --- Private Helpers ---
def _clean_titles(titles: list, limit: int = 10) -> list:
    seen = set()
    clean = []
    for h in titles:
        h = h.strip().lstrip("*").strip()
        if len(h) < 15:
            continue
        if h.startswith("/") or h.startswith("\\") or h.startswith("C:"):
            continue
        key = h.lower()[:40]
        if key not in seen:
            seen.add(key)
            clean.append(h[:80])
    return clean[-limit:]

def _get_confirmed_titles(table: str, limit: int = 5) -> list[str]:
    """Get CONFIRMED items (conf>=0.75, ec>=3, valid). Ground truth anchors."""
    try:
        from pulse.core.brain_db import get_conn
        rows = get_conn().execute(
            f"SELECT title FROM {table} WHERE source_type='CONFIRMED' AND confidence>=0.75"
            f" AND evidence_count>=3 AND validity='valid' ORDER BY confidence DESC LIMIT ?",
            (limit,)).fetchall()
        return _clean_titles([r[0] if isinstance(r, (tuple, list)) else r["title"] for r in rows], limit=limit) if rows else []
    except Exception:
        return []

def _get_recent_titles(table: str, limit: int = 10) -> list[str] | None:
    """Get most recent titles from SQLite (sub-ms vs 11.3MB JSONL scan). Returns list of cleaned titles on success, None on failure (triggers JSONL fallback)."""
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        rows = conn.execute(
            f"SELECT title FROM {table} ORDER BY timestamp DESC LIMIT ?",
            (limit,)
        ).fetchall()
        if rows:
            titles = [r[0] if isinstance(r, (tuple, list)) else r["title"] for r in rows]
            return _clean_titles(titles, limit=limit)
    except Exception:
        pass
    return None

def _load_jsonl_titles(jsonl_path, limit: int = 10) -> list[str]:
    """Fallback: load titles from JSONL using shared extractor (scans full file)."""
    items = extract_jsonl_titles(jsonl_path, max_chars=80, min_chars=15)
    titles = [item["title"] for item in items]
    return _clean_titles(titles, limit=limit)

def _extract_search_hits(hits, limit: int = 5) -> list:
    out = []
    for h in hits[:limit]:
        text = (h.get("text") or h.get("title", ""))[:80].strip()
        if text and len(text) > 10:
            out.append(text)
    return out

# --- Public Loaders ---
def load_recent_brain() -> dict:
    """Load recent brain data. IMPERATIVE — injected whether agents ask or not."""
    result = {"lessons": [], "failures": [], "anti_patterns": [], "patterns": [], "user_profile": {}, "confirmed_ground_truth": []}

    # Load User Profile (The distilled "Forgetfulness" Engine output)
    profile_file = STATE_DIR / "user_profile.json"
    if profile_file.exists():
        try:
            profile_data = load_json_dict(profile_file)
            result["user_profile"] = profile_data
        except Exception:
            pass

    # SQLite fast-path (sub-ms) with JSONL fallback (11.3MB scan)
    result["lessons"] = _get_recent_titles("lessons", 10) or _load_jsonl_titles(LESSONS_JSONL, 10)
    result["failures"] = _get_recent_titles("failures", 10) or _load_jsonl_titles(FAILS_JSONL, 10)

    # Anti-patterns (from JSON registry — unchanged, not JSONL)
    ap_file = ANTI_PATTERNS_DIR / "anti_patterns_active.json"
    if ap_file.exists():
        ap_items = load_json(ap_file)
        if isinstance(ap_items, list):
            # Sort by evidence count (highest first) so agents see the most
            # validated anti-patterns, not the oldest noise entries
            active = [ap for ap in ap_items if ap.get("status") == "active"]
            active.sort(key=lambda ap: ap.get("evidence_count", 0), reverse=True)
            patterns = [f"{ap.get('description', '')[:60]} -> {ap.get('rule', '')[:60]}"
                        for ap in active]
            result["anti_patterns"] = patterns[:10]

    result["patterns"] = _get_recent_titles("patterns", 5) or _load_jsonl_titles(PATTERNS_JSONL, 5)

    # Ground truth anchors — CONFIRMED items across lessons + patterns + failures (top 5 total)
    confirmed: list[str] = []
    for _tbl in ("lessons", "patterns", "failures"):
        confirmed.extend(_get_confirmed_titles(_tbl, limit=3))
    # Deduplicate while preserving order, keep top 5
    seen_gt: set[str] = set()
    deduped_gt: list[str] = []
    for t in confirmed:
        key = t.lower()[:40]
        if key not in seen_gt:
            seen_gt.add(key)
            deduped_gt.append(t)
    result["confirmed_ground_truth"] = deduped_gt[:5]

    # Decisions (N0 — past choices with context)
    try:
        from pulse.brain.decision_store import _load_decisions
        entries = _load_decisions()
        active = [e for e in entries if e.get("status") == "active"]
        active.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
        result["decisions"] = []
        for e in active[:5]:
            alt_text = f" over {', '.join(e.get('alternatives_rejected', []))}" if e.get('alternatives_rejected') else ""
            reason = f" -- {e.get('reasoning', '')[:40]}" if e.get('reasoning') else ""
            result["decisions"].append(
                f"{e.get('choice', '')[:50]}{alt_text}{reason} (scope: {e.get('scope', '?')})"
            )
    except Exception:
        pass

    # Item 96: Temporal context reinstatement — inject last session's working memory
    try:
        from pulse.brain.working_memory import get_active_contexts
        wm_items = get_active_contexts("search")
        if wm_items:
            result["working_memory_context"] = [it.get("text", "")[:100] for it in wm_items[:5]]
    except Exception:
        pass

    return result

def load_relevant_brain(task_query: str, task_type: str = "") -> dict:
    """Load brain knowledge RELEVANT to the task via brain_search.

    Phase 4: task_type enables credit-driven attention weights and
    prioritize_by_credit() reranking so high-value items surface first.
    """
    if not brain_search or not task_query or len(task_query) < 5:
        return load_recent_brain()

    profile_file = STATE_DIR / "user_profile.json"
    user_profile = load_json_dict(profile_file) if profile_file.exists() else {}

    try:
        search_result = brain_search(
            task_query, max_per_source=5,
            task_type=task_type or "general")
        hits = search_result.get("results", {})
        total = search_result.get("total_results", 0)

        if total < 3:
            recent_data = load_recent_brain()
            recent_data["user_profile"] = user_profile
            return recent_data

        result = {
            "lessons": _extract_search_hits(hits.get("wins", []), 5),
            "failures": _extract_search_hits(hits.get("fails", []), 5),
            "anti_patterns": _extract_search_hits(hits.get("anti_patterns", []), 5),
            "patterns": _extract_search_hits(hits.get("domains", []), 3),
            "decisions": _extract_search_hits(hits.get("decisions", []), 3),
            "user_profile": user_profile,
        }

        # Phase 4: Rerank lists by accumulated reward credit
        try:
            from pulse.brain.learned_prompt import prioritize_by_credit
            for key in ("lessons", "failures", "patterns"):
                if result[key]:
                    result[key] = prioritize_by_credit(result[key], limit=len(result[key]))
        except Exception:
            pass  # Cold start or missing ledger — no reranking

        return result
    except Exception:
        return load_recent_brain()

def load_agent_procedures(agent_id: str) -> dict:
    """Load agent-specific procedure recommendations. Returns empty if insufficient data."""
    if not agent_id:
        return {}
    try:
        from pulse.brain.agent_procedures import (
            get_agent_profile, match_procedures, get_weak_domains,
        )
        profile = get_agent_profile(agent_id)
        if profile.get("total_tasks", 0) < 3:
            return {}
        return {
            "weak_domains": get_weak_domains(agent_id),
            "procedures": match_procedures(agent_id),
            "profile_summary": profile,
        }
    except Exception:
        return {}

def load_daemon_injections(max_items: int = 5, agent_name: str = "") -> dict:
    """Load D4 constraints + D5 gate feedback + D6 golden paths + U7 cross-agent intel. Returns: {"synthetic_constraints": [...], "gate_feedback": [...], "golden_paths": [...], "cross_agent_intel": [...]}"""
    result: dict = {"synthetic_constraints": [], "gate_feedback": [], "golden_paths": [], "cross_agent_intel": []}

    # D4: Synthetic constraints (precognitive guardrails)
    constraints_file = STATE_DIR / "synthetic_constraints.json"
    if constraints_file.exists():
        try:
            data = load_json_dict(constraints_file)
            active = [c for c in data.get("constraints", [])
                      if c.get("status") == "active"]
            active.sort(key=lambda c: -c.get("confidence", 0))
            result["synthetic_constraints"] = active[:max_items]
        except Exception:
            pass

    # D5: Gate feedback (temporary anti-patterns from swarm failures)
    feedback_file = STATE_DIR / "gate_feedback.json"
    if feedback_file.exists():
        try:
            from datetime import datetime, timezone
            data = load_json_dict(feedback_file)
            now = datetime.now(timezone.utc)
            active = []
            for e in data.get("entries", []):
                if e.get("status") != "active":
                    continue
                try:
                    expires = datetime.fromisoformat(e.get("expires_at", ""))
                    if expires.tzinfo is None:
                        expires = expires.replace(tzinfo=timezone.utc)
                    if expires < now:
                        continue
                except (ValueError, TypeError):
                    pass
                active.append(e)
            result["gate_feedback"] = active[:max_items]
        except Exception:
            pass

    # D6: Golden paths (proven success strategies from pathweaver)
    golden_file = STATE_DIR / "golden_paths.json"
    if golden_file.exists():
        try:
            data = load_json_dict(golden_file)
            active = [p for p in data.get("paths", [])
                      if p.get("status") == "active"]
            active.sort(key=lambda p: -p.get("confidence", 0))
            result["golden_paths"] = active[:max_items]
        except Exception:
            pass

    # U7: Cross-agent learning (Dr. Mirror Neurons)
    result["cross_agent_intel"] = _load_cross_agent_intel(agent_name, max_items)

    return result

def _load_cross_agent_intel(current_agent: str = "", limit: int = 5) -> list:
    """Load lessons/failures from OTHER agents for cross-pollination (U7)."""
    try:
        from pulse.brain.cross_agent import extract_cross_agent_intel
        return extract_cross_agent_intel(current_agent, limit)
    except Exception:
        return []

def load_curriculum(domain: str = "", task_query: str = "") -> dict:
    """Load domain curriculum from D15 Phylogenetic Skill Compiler output.

    Tries direct domain match first, then infers from task_query keywords.
    Returns curriculum dict or {} if unavailable.
    """
    curricula_file = HIPPOCAMPUS_DIR / "Semantic" / "curricula.json"
    if not curricula_file.exists():
        return {}
    try:
        data = load_json_dict(curricula_file)
        cur = data.get("curricula", {})
        if not cur:
            return {}
        # Direct domain match
        if domain and domain.lower() in cur:
            result = cur[domain.lower()]
            result["_generation"] = data.get("generation", 0)
            return result
        # Infer from task_query keywords
        if task_query:
            query_lower = task_query.lower()
            for d, c in cur.items():
                if d in query_lower:
                    c["_generation"] = data.get("generation", 0)
                    return c
        return {}
    except Exception:
        return {}

def load_metamemory_and_self_model() -> dict:
    """Load metamemory domain map + self-model summary into boot briefing."""
    result = {"competence_summary": [], "limitations": [], "knowledge_gaps": []}

    # Metamemory: domain competence levels
    try:
        from pulse.brain.metamemory import get_metamemory
        meta = get_metamemory()
        for domain, stats in meta.items():
            level = stats.get("competence_level", "unknown")
            result["competence_summary"].append(f"{level.upper()} in {domain}")
            if stats.get("gap_flag"):
                result["knowledge_gaps"].append(domain)
    except Exception:
        pass

    # Self-model: limitations + capabilities
    try:
        from pulse.brain.self_model import get_self_model
        model = get_self_model()
        result["limitations"] = model.get("limitations", [])
    except Exception:
        pass

    return result

def _search_brain_for_task(task_title: str) -> dict:
    """Lightweight brain search for task context."""
    return brain_search(task_title, max_per_source=2) if brain_search else {}
def load_session_continuity(agent: str = "claude") -> str:
    """P2-4: Load prior session checkpoint and return a ## PREVIOUS SESSION STATE section.

    Called during boot. If a checkpoint < 24h old exists for this agent, injects
    the task, progress, findings, and files_touched so the agent resumes with context.
    Returns empty string if no recent checkpoint exists.
    """
    try:
        from pulse.brain.session_checkpoint import load_latest_checkpoint
        cp = load_latest_checkpoint(agent)
        if not cp:
            return ""
        lines = ["## PREVIOUS SESSION STATE"]
        lines.append(f"Task:     {cp.get('task', 'unknown')[:100]}")
        lines.append(f"Progress: {cp.get('progress', 'unknown')[:120]}")
        findings = cp.get("findings", [])
        if findings:
            lines.append("Findings:")
            for f in findings[:5]:
                lines.append(f"  - {str(f)[:100]}")
        files = cp.get("files_touched", [])
        if files:
            lines.append(f"Files touched: {', '.join(str(f)[:60] for f in files[:5])}")
        ts = cp.get("timestamp", "")[:19]
        lines.append(f"Saved: {ts} UTC")
        return "\n".join(lines)
    except Exception:
        return ""


def load_swarm_competence() -> str:
    """Phase 5E: Theory of Mind — agent strength/weakness profiles."""
    try:
        hf = get_state_dir() / "hebbian_matrix.json"
        if not hf.exists(): return ""
        data = json.loads(hf.read_text(encoding="utf-8"))
        if not data: return ""
        lines = ["Agent Competence Profile:"]
        for aid, doms in data.get("matrix", {}).items():
            if not isinstance(doms, dict): continue
            ents = []
            for d, s in doms.items():
                if not isinstance(s, dict): continue
                att = s.get("successes", 0) + s.get("failures", 0)
                if att >= 3:
                    ents.append((d, s.get("successes", 0) / att, att))
            if ents:
                ents.sort(key=lambda x: -x[1])
                parts = [f"{int(r*100)}% on {d} ({a} tasks)" for d, r, a in ents[:3]]
                lines.append(f"- {aid}: {', '.join(parts)}")
        return "\n".join(lines) if len(lines) > 1 else ""
    except Exception:
        return ""
