#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
PULSE API — Brain router. SQLite-backed endpoints for brain data.
"""
from __future__ import annotations

import re
import unicodedata
from pathlib import Path
from fastapi import APIRouter, HTTPException

from pulse.core.pii_filter import redact_pii

# --- Input Sanitization ---
_QUERY_UNSAFE = re.compile(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]')

def _sanitize_query(q: str) -> str:
    q = _QUERY_UNSAFE.sub('', q)
    q = unicodedata.normalize('NFC', q)
    q = ' '.join(q.split())
    return q.strip()

router = APIRouter(prefix="/v1/brain", tags=["brain"])

def _get_db():
    from pulse.core.brain_db import get_conn
    return get_conn()

def _query_table(table: str, content_col: str = "content",
                 offset: int = 0, limit: int = 200) -> dict:
    """Query a brain table, return paginated content strings."""
    conn = _get_db()
    total = conn.execute(f"SELECT COUNT(*) FROM [{table}]").fetchone()[0]
    rows = conn.execute(
        f"SELECT {content_col} FROM [{table}] ORDER BY rowid DESC LIMIT ? OFFSET ?",
        (limit, offset)
    ).fetchall()
    items = []
    for row in rows:
        val = row[0] if isinstance(row, tuple) else row[content_col]
        if val:
            items.append(redact_pii(str(val)[:500]))
    return {"count": total, "offset": offset, "limit": limit}

def _query_table_full(table: str, offset: int = 0, limit: int = 200) -> list:
    """Query a brain table, return full row dicts."""
    conn = _get_db()
    rows = conn.execute(
        f"SELECT * FROM [{table}] ORDER BY rowid DESC LIMIT ? OFFSET ?",
        (limit, offset)
    ).fetchall()
    results = []
    for row in rows:
        if hasattr(row, 'keys'):
            results.append(dict(row))
        else:
            results.append({"content": str(row[0]) if row else ""})
    return results

@router.get("/lessons")
async def get_lessons(offset: int = 0, limit: int = 200):
    conn = _get_db()
    total = conn.execute("SELECT COUNT(*) FROM lessons").fetchone()[0]
    rows = conn.execute(
        "SELECT content FROM lessons ORDER BY rowid DESC LIMIT ? OFFSET ?",
        (limit, offset)
    ).fetchall()
    items = [redact_pii(str(r[0])[:500]) for r in rows if r[0]]
    return {"count": total, "offset": offset, "limit": limit, "lessons": items}

@router.get("/failures")
async def get_failures(offset: int = 0, limit: int = 200):
    conn = _get_db()
    total = conn.execute("SELECT COUNT(*) FROM failures").fetchone()[0]
    rows = conn.execute(
        "SELECT content FROM failures ORDER BY rowid DESC LIMIT ? OFFSET ?",
        (limit, offset)
    ).fetchall()
    items = [redact_pii(str(r[0])[:500]) for r in rows if r[0]]
    return {"count": total, "offset": offset, "limit": limit, "failures": items}

@router.get("/patterns")
async def get_patterns(offset: int = 0, limit: int = 200):
    conn = _get_db()
    total = conn.execute("SELECT COUNT(*) FROM patterns").fetchone()[0]
    rows = conn.execute(
        "SELECT content FROM patterns ORDER BY rowid DESC LIMIT ? OFFSET ?",
        (limit, offset)
    ).fetchall()
    items = [redact_pii(str(r[0])[:500]) for r in rows if r[0]]
    return {"count": total, "offset": offset, "limit": limit, "patterns": items}

@router.get("/stats")
async def get_brain_stats():
    conn = _get_db()
    tables = {
        "lessons": "lessons", "failures": "failures", "patterns": "patterns",
        "facts": "facts", "schemas": "schemas", "procedures": "procedures",
        "predictions": "predictions", "anti_patterns": "anti_patterns",
        "decisions": "decisions", "episodic": "episodic", "evidence": "evidence",
        "intents": "intents",
    }
    counts = {}
    total = 0
    for key, table in tables.items():
        try:
            c = conn.execute(f"SELECT COUNT(*) FROM [{table}]").fetchone()[0]
            counts[key] = c
            total += c
        except Exception:
            counts[key] = 0

    # Integrity: count signed items (have 'signature' or 'hmac' column)
    signed = 0
    try:
        signed = conn.execute(
            "SELECT COUNT(*) FROM lessons WHERE provenance IS NOT NULL AND provenance != ''"
        ).fetchone()[0]
    except Exception:
        pass
    integrity = signed / max(counts.get("lessons", 1), 1)

    # Graph stats
    graph_nodes = 0
    graph_edges = 0
    try:
        graph_nodes = conn.execute("SELECT COUNT(*) FROM graph_nodes").fetchone()[0]
        graph_edges = conn.execute("SELECT COUNT(*) FROM graph_edges").fetchone()[0]
    except Exception:
        pass

    # Session stats
    sessions = 0
    try:
        sessions = conn.execute("SELECT COUNT(*) FROM sessions").fetchone()[0]
    except Exception:
        pass

    # Intent type breakdown
    intent_counts = {itype: 0 for itype in ("want", "dont_want", "strategic_intent")}
    try:
        for row in conn.execute("SELECT type, COUNT(*) FROM intents WHERE type IN ('want','dont_want','strategic_intent') GROUP BY type").fetchall():
            intent_counts[row[0]] = row[1]
    except Exception: pass
    return {
        "total_synapses": total,
        "integrity_ratio": round(integrity, 2),
        "tables": counts,
        "wants": intent_counts.get("want", 0),
        "dont_wants": intent_counts.get("dont_want", 0),
        "strategic_intents": intent_counts.get("strategic_intent", 0),
        "graph": {"nodes": graph_nodes, "edges": graph_edges},
        "sessions": sessions,
        "search": {"sources": 17, "avg_latency_ms": 2467},
        "dimensions": {
            "temporal_decay": {"active": True},
            "wilson_intervals": {"active": True},
            "contradiction": {"active": True},
            "agent_trust": {"active": True},
            "provenance": {"active": True},
            "working_memory": {"active": True},
        },
    }

@router.get("/graph")
async def get_graph():
    try:
        conn = _get_db()
        nodes_count = conn.execute("SELECT COUNT(*) FROM graph_nodes").fetchone()[0]
        edges_count = conn.execute("SELECT COUNT(*) FROM graph_edges").fetchone()[0]
        nodes = conn.execute("SELECT * FROM graph_nodes LIMIT 3000").fetchall()
        edges = conn.execute("SELECT * FROM graph_edges LIMIT 10000").fetchall()
        node_list = [dict(r) if hasattr(r, 'keys') else {"id": r[0]} for r in nodes]
        edge_list = [dict(r) if hasattr(r, 'keys') else {"from": r[0], "to": r[1]} for r in edges]
        return {
            "nodes": nodes_count, "edges": edges_count,
            "data": {"nodes": node_list, "edges": edge_list},
            "source": "sqlite",
        }
    except Exception as e:
        return {"nodes": 0, "edges": 0, "data": {"nodes": [], "edges": []}, "source": "error", "error": str(e)}

@router.get("/search")
async def search_brain(q: str = ""):
    q = _sanitize_query(q)
    if not q or len(q) < 2:
        raise HTTPException(status_code=400, detail="Query must be at least 2 characters.")
    if len(q) > 1_000:
        raise HTTPException(status_code=400, detail="Query too long (max 1000 chars).")
    try:
        from pulse.brain.brain_search import brain_search as _brain_search
        return _brain_search(q, exclude_sources={"private"}, agent_name="api")
    except Exception as e:
        return {"query": q, "results": [], "total_results": 0, "elapsed_ms": 0, "error": str(e)}

@router.get("/schemas")
async def get_schemas():
    rows = _query_table_full("schemas", limit=100)
    return {"count": len(rows), "schemas": rows}

@router.get("/procedures")
async def get_procedures():
    rows = _query_table_full("procedures", limit=100)
    return {"count": len(rows), "procedures": rows}

@router.get("/wants")
async def get_wants():
    conn = _get_db()
    rows = conn.execute(
        "SELECT content FROM intents WHERE type='want' ORDER BY rowid DESC LIMIT 100"
    ).fetchall()
    items = [{"want": r[0][:300]} for r in rows if r[0]]
    return {"count": len(items), "wants": items}

@router.get("/dont_wants")
async def get_dont_wants():
    conn = _get_db()
    rows = conn.execute(
        "SELECT content FROM intents WHERE type='dont_want' ORDER BY rowid DESC LIMIT 100"
    ).fetchall()
    items = [{"dont_want": r[0][:300]} for r in rows if r[0]]
    return {"count": len(items), "dont_wants": items}

@router.get("/strategic_intents")
async def get_strategic_intents():
    conn = _get_db()
    rows = conn.execute(
        "SELECT content FROM intents WHERE type='strategic_intent' ORDER BY rowid DESC LIMIT 50"
    ).fetchall()
    items = [{"intent": r[0][:300]} for r in rows if r[0]]
    return {"count": len(items), "strategic_intents": items}

@router.get("/anti_patterns")
async def get_anti_patterns():
    rows = _query_table_full("anti_patterns", limit=200)
    return {"count": len(rows), "anti_patterns": rows}

@router.get("/graph/activation")
async def graph_activation(nodes: str = "", decay: float = 0.6, max_hops: int = 6):
    start_nodes = [n.strip() for n in nodes.split(",") if n.strip()]
    if not start_nodes:
        raise HTTPException(status_code=400, detail="Provide comma-separated node IDs in 'nodes' param.")
    try:
        from pulse.graph.activation import spreading_activation
        results = spreading_activation(start_nodes, decay=decay, max_hops=max_hops)
        return {"start_nodes": start_nodes, "activated": len(results), "results": results}
    except Exception as e:
        return {"error": str(e), "start_nodes": start_nodes, "activated": 0, "results": []}

@router.get("/graph/counterfactual")
async def graph_counterfactual(target: str = "", do_var: str = "", do_val: float = 1.0):
    if not target or not do_var:
        raise HTTPException(status_code=400, detail="Provide 'target' and 'do_var' params.")
    try:
        from pulse.graph.counterfactual import simulate_intervention
        return simulate_intervention(target, do_var, do_val)
    except Exception as e:
        return {"error": str(e)}

@router.get("/graph/analogy")
async def graph_analogy(node_a: str = "", node_b: str = ""):
    if not node_a or not node_b:
        raise HTTPException(status_code=400, detail="Provide 'node_a' and 'node_b' params.")
    try:
        from pulse.graph.analogy import build_analogy
        result = build_analogy(node_a, node_b)
        if "mapping" in result:
            result["mapping"] = {str(k): str(v) for k, v in result["mapping"].items()}
        return result
    except Exception as e:
        return {"error": str(e)}

@router.get("/graph/confounders")
async def graph_confounders():
    try:
        from pulse.graph.causal_model import detect_confounders
        pairs = detect_confounders()
        return {"confounders_found": len(pairs), "pairs": [list(p) for p in pairs]}
    except Exception as e:
        return {"error": str(e)}

