"""brain_db.py -- Runtime DB operations: connection, insert, upsert, search.

Schema constants and DDL (init_db, _KNOWLEDGE_COLS, _VALID_TABLES, etc.) live in
brain_db_schema.py.  Everything is re-exported here so all existing callers
continue to work unchanged.
"""
import math
import sqlite3
import threading
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional, List, Dict
from pulse.core.paths import get_state_dir, get_brain_dir
from pulse.core.brain_utils import _make_fingerprint as make_fingerprint
from pulse.core.brain_io import _acquire
from pulse.core.brain_db_schema import (
    _VALID_TABLES,
    _validate_table,
    _KNOWLEDGE_COLS,
    _knowledge_indexes,
    _tables_exist,
    init_db as _init_db_schema,
)

# Re-export schema symbols for backward compatibility.
__all__ = [
    "DB_PATH", "get_conn", "init_db", "force_checkpoint",
    "insert_knowledge", "upsert_knowledge", "bulk_upsert", "search",
    "_VALID_TABLES", "_validate_table", "_KNOWLEDGE_COLS",
    "_knowledge_indexes", "_tables_exist",
]

DB_PATH = get_brain_dir() / "pulse_brain.db"
_local = threading.local()
_db_initialized = False
_init_lock = threading.Lock()

# Item 55: Critical period tracking (P14 Neuroplasticity)
_CRITICAL_PERIOD_DOMAINS: dict = {}
_CRITICAL_PERIOD_SIZE = 10
# S37: protect _CRITICAL_PERIOD_DOMAINS mutations in hot path
_cp_lock = threading.Lock()

def force_checkpoint() -> bool:
    """Force a WAL TRUNCATE checkpoint. Returns True if no blockers."""
    try:
        conn = get_conn()
        result = conn.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()
        return result is not None and result[0] == 0
    except Exception:
        return False

def get_conn() -> sqlite3.Connection:
    if not hasattr(_local, "conn"):
        DB_PATH.parent.mkdir(parents=True, exist_ok=True)
        conn = sqlite3.connect(str(DB_PATH.resolve()), check_same_thread=False)
        try:
            import sqlite_vec
            conn.enable_load_extension(True)
            sqlite_vec.load(conn)
            conn.enable_load_extension(False)
        except Exception:
            pass
        conn.execute("PRAGMA journal_mode=WAL;")
        conn.execute("PRAGMA synchronous=NORMAL;")
        conn.execute("PRAGMA busy_timeout=60000;")  # P47 FIX: 60s (was 5s)
        conn.execute("PRAGMA wal_autocheckpoint=200;")
        conn.create_function("SQRT", 1, math.sqrt)
        conn.row_factory = sqlite3.Row
        _local.conn = conn
    global _db_initialized
    if not _db_initialized:
        # Fast path: skip heavy init_db if tables already exist.
        if _tables_exist(_local.conn):
            _db_initialized = True
        else:
            with _acquire(DB_PATH):
                with _init_lock:
                    if not _db_initialized:
                        if _tables_exist(_local.conn):
                            _db_initialized = True
                        else:
                            try:
                                init_db()
                                _db_initialized = True
                            except Exception:
                                raise
    return _local.conn

def init_db() -> None:
    """Create all tables and indexes. Uses the thread-local connection."""
    # Do NOT call get_conn() here -- causes infinite recursion
    _init_db_schema(_local.conn)

def insert_knowledge(table: str, data: Dict[str, Any]) -> Optional[str]:
    """Insert knowledge (idempotent via UNIQUE fingerprint)."""
    _validate_table(table)
    # LLM-enhanced quality gate on intents/facts/evidence/anti_patterns (Audit 5 P0 + LLM upgrade)
    try:
        if data.get("content") and table in ("intents", "facts", "evidence", "anti_patterns"):
            from pulse.brain.save_writers.llm_quality_gate import llm_enhanced_quality_gate
            _sal = float(data.get("salience", 0.0))
            _conf = float(data.get("confidence", 0.0))
            if not llm_enhanced_quality_gate(data["content"], table, _sal, _conf): return "quality_rejected"
    except Exception: pass
    # BCM gate: suppress low-salience writes during high load
    try:
        signals = __import__("pulse.core.interoception", fromlist=["get_signals"]).get_signals()
        if signals.get("agent_load", 0) > 15 and float(data.get("salience", 1.0)) < 0.5:
            import logging as _bcm_log
            _bcm_log.getLogger("pulse.brain_db").debug("BCM gate: shedding low-salience item under load")
            return "bcm_shed"
    except Exception:
        pass
    conn = get_conn()
    content = data.get("content", "")
    fingerprint = data.get("fingerprint") or (make_fingerprint(content) if content else "null")
    _TABLE_PREFIX = {"lessons": "L", "failures": "F", "patterns": "P", "facts": "FA",
                     "intents": "I", "evidence": "EV", "decisions": "D", "private": "V"}
    item_id = data.get("id") or f"{_TABLE_PREFIX.get(table, 'K')}-{fingerprint[:8]}"
    tags = json.dumps(data.get("tags", [])) if isinstance(data.get("tags"), list) else data.get("tags", "[]")
    cross_refs = json.dumps(data.get("cross_refs", [])) if isinstance(data.get("cross_refs"), list) else data.get("cross_refs", "[]")
    prov = json.dumps(data.get("_provenance", data.get("provenance", {})))
    meta = json.dumps(data.get("metadata", {})) if isinstance(data.get("metadata"), dict) else data.get("metadata", "{}")
    title = data.get("title", "")[:80]
    domain = data.get("domain", "general")
    agent = data.get("agent", "unknown")
    ts = data.get("timestamp", datetime.now(timezone.utc).isoformat())
    # Item 122: Catastrophic forgetting protection
    try:
        from pulse.core.forgetting_protection import protect_high_confidence
        item_id, fingerprint = protect_high_confidence(conn, table, fingerprint, item_id, ts)
    except Exception:
        pass

    import time as _time_mod
    for _retry in range(5):  # P47 FIX: 5 retries with longer backoff
        try:
            with conn:
                conn.execute(
                    f"INSERT OR IGNORE INTO {table} "
                    "(id, fingerprint, type, content, title, domain, agent, timestamp, "
                    "salience, confidence, consolidation_count, evidence_count, "
                    "access_count, decay_score, validity, tags, cross_refs, provenance, metadata) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (item_id, fingerprint, data.get("type", "lesson"), content,
                     title, domain, agent, ts,
                     float(data.get("salience", 1.0)), float(data.get("confidence", 0.5)),
                     int(data.get("consolidation_count", 1)), int(data.get("evidence_count", 1)),
                     int(data.get("access_count", 0)), float(data.get("decay_score", 1.0)),
                     data.get("validity", "valid"), tags, cross_refs, prov, meta))
                # FTS indexing handled by AFTER INSERT triggers in brain_db_schema.py.
                # Manual INSERT here was causing double FTS entries (QR8 P2 critical bug).
                pass  # triggers fire automatically
            break
        except sqlite3.OperationalError as _db_err:
            if "database is locked" in str(_db_err) and _retry < 4:
                _time_mod.sleep(2.0 * (_retry + 1))  # P47 FIX: backoff 2s, 4s, 6s, 8s
                continue
            raise
    # Item 55: Critical period for new domains (P14 Neuroplasticity)
    try:
        with _cp_lock:
            if domain not in _CRITICAL_PERIOD_DOMAINS:
                existing = conn.execute(f"SELECT COUNT(*) FROM {table} WHERE domain=?", (domain,)).fetchone()[0]
                if existing <= 1:
                    _CRITICAL_PERIOD_DOMAINS[domain] = _CRITICAL_PERIOD_SIZE
            if _CRITICAL_PERIOD_DOMAINS.get(domain, 0) > 0:
                conn.execute(f"UPDATE {table} SET salience=MIN(salience*2.0,10.0) WHERE id=?", (item_id,))
                conn.commit()
                _CRITICAL_PERIOD_DOMAINS[domain] -= 1
                if _CRITICAL_PERIOD_DOMAINS[domain] <= 0:
                    del _CRITICAL_PERIOD_DOMAINS[domain]
    except Exception:
        pass
    # F23: sub-threshold trace -- 3+ low-salience appearances -> promote
    try:
        row = conn.execute(
            f"SELECT salience, consolidation_count FROM {table} WHERE fingerprint = ?",
            (fingerprint,)).fetchone()
        if row and 0.1 <= float(row["salience"]) <= 0.4 and int(row["consolidation_count"]) >= 3:
            with conn:
                conn.execute(f"UPDATE {table} SET salience = 1.0 WHERE fingerprint = ?", (fingerprint,))
    except Exception:
        pass

    return item_id

def upsert_knowledge(table: str, data: dict) -> bool:
    """INSERT OR REPLACE a knowledge item into SQLite + FTS. For consolidation rewrites."""
    _validate_table(table)
    conn = get_conn()
    content = data.get("content", "")
    fp = data.get("fingerprint") or (make_fingerprint(content) if content else "null")
    item_id = data.get("id") or f"L-{fp[:8]}"
    tags = json.dumps(data.get("tags", [])) if isinstance(data.get("tags"), list) else data.get("tags", "[]")
    xrefs = json.dumps(data.get("cross_refs", [])) if isinstance(data.get("cross_refs"), list) else data.get("cross_refs", "[]")
    prov = json.dumps(data.get("_provenance", data.get("provenance", {})))
    meta = json.dumps(data.get("metadata", {})) if isinstance(data.get("metadata"), dict) else data.get("metadata", "{}")
    import time as _t
    for _retry in range(3):
        try:
            with conn:
                conn.execute(
                    f"INSERT OR REPLACE INTO {table} "
                    "(id, fingerprint, type, content, title, domain, agent, timestamp, "
                    "salience, confidence, consolidation_count, evidence_count, "
                    "access_count, decay_score, validity, tags, cross_refs, provenance, metadata) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (item_id, fp, data.get("type", "lesson"), content,
                     data.get("title", "")[:80], data.get("domain", "general"),
                     data.get("agent", "unknown"),
                     data.get("timestamp", datetime.now(timezone.utc).isoformat()),
                     float(data.get("salience", 1.0)), float(data.get("confidence", 0.5)),
                     int(data.get("consolidation_count", 1)), int(data.get("evidence_count", 1)),
                     int(data.get("access_count", 0)), float(data.get("decay_score", 1.0)),
                     data.get("validity", "valid"), tags, xrefs, prov, meta))
                conn.execute("DELETE FROM knowledge_fts WHERE item_id = ? AND table_name = ?",
                             (item_id, table))
                conn.execute(
                    "INSERT INTO knowledge_fts (item_id, content, title, domain, agent, table_name) "
                    "VALUES (?, ?, ?, ?, ?, ?)",
                    (item_id, content, data.get("title", "")[:80],
                     data.get("domain", "general"), data.get("agent", "unknown"), table))
            return True
        except sqlite3.OperationalError as _e:
            if "database is locked" in str(_e) and _retry < 2:
                _t.sleep(1.0 * (_retry + 1))
                continue
            return False
    return False

# bulk_upsert and search extracted to brain_db_helpers.py for Law 7 compliance.
from .brain_db_helpers import bulk_upsert, search

# ── JSON Kill List: SQLite query helpers ────────────────────────────
# These replace direct JSON file reads (wants.json, dont_wants.json,
# anti_patterns_active.json). Return dicts with legacy key names for compat.

def _get_wants(limit: int = 500) -> list:
    """Get wants from intents table. Returns dicts with 'want' key for compat."""
    conn = get_conn()
    rows = conn.execute(
        "SELECT content, domain, confidence, salience FROM intents "
        "WHERE type='want' ORDER BY rowid DESC LIMIT ?", (limit,)
    ).fetchall()
    return [{"want": r["content"], "domain": r["domain"],
             "confidence": r["confidence"], "salience": r["salience"]} for r in rows]

def _get_dont_wants(limit: int = 500) -> list:
    """Get dont_wants from intents table. Returns dicts with 'dont_want' key."""
    conn = get_conn()
    rows = conn.execute(
        "SELECT content, domain, confidence, salience FROM intents "
        "WHERE type='dont_want' ORDER BY rowid DESC LIMIT ?", (limit,)
    ).fetchall()
    return [{"dont_want": r["content"], "domain": r["domain"],
             "confidence": r["confidence"], "salience": r["salience"]} for r in rows]

def _get_strategic_intents(limit: int = 100) -> list:
    """Get strategic intents from intents table."""
    conn = get_conn()
    rows = conn.execute(
        "SELECT content, title, domain, confidence, salience, metadata FROM intents "
        "WHERE type='strategic_intent' ORDER BY rowid DESC LIMIT ?", (limit,)
    ).fetchall()
    results = []
    for r in rows:
        d = {"title": r["title"] or r["content"][:80], "content": r["content"],
             "domain": r["domain"], "confidence": r["confidence"]}
        # Parse status from metadata if present
        meta = r["metadata"]
        if meta:
            try:
                md = json.loads(meta) if isinstance(meta, str) else meta
                d["status"] = md.get("status", "pending")
            except Exception:
                d["status"] = "pending"
        else:
            d["status"] = "pending"
        results.append(d)
    return results

def _get_active_anti_patterns(limit: int = 500) -> list:
    """Get anti-patterns from SQLite. Returns dicts compatible with JSON shape."""
    conn = get_conn()
    rows = conn.execute(
        "SELECT * FROM anti_patterns ORDER BY evidence_count DESC LIMIT ?", (limit,)
    ).fetchall()
    return [dict(r) for r in rows]

# Public aliases for JSON Kill List helpers (must be after function definitions)
get_wants = _get_wants
get_dont_wants = _get_dont_wants
get_strategic_intents = _get_strategic_intents
get_active_anti_patterns = _get_active_anti_patterns