import math
import sqlite3
import threading
import json
import hashlib
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional, List, Dict

from pulse.core.paths import get_state_dir

DB_PATH = get_state_dir() / "db" / "pulse_brain.db"
_local = threading.local()

_KNOWLEDGE_COLS = """
    id                  TEXT PRIMARY KEY,
    fingerprint         TEXT NOT NULL,
    type                TEXT NOT NULL DEFAULT 'lesson',
    content             TEXT NOT NULL,
    title               TEXT NOT NULL DEFAULT '',
    domain              TEXT NOT NULL DEFAULT 'general',
    agent               TEXT NOT NULL DEFAULT 'unknown',
    timestamp           TEXT NOT NULL DEFAULT '',
    salience            REAL NOT NULL DEFAULT 1.0,
    confidence          REAL NOT NULL DEFAULT 0.5,
    consolidation_count INTEGER NOT NULL DEFAULT 1,
    evidence_count      INTEGER NOT NULL DEFAULT 1,
    access_count        INTEGER NOT NULL DEFAULT 0,
    last_accessed       TEXT DEFAULT NULL,
    decay_score         REAL NOT NULL DEFAULT 1.0,
    validity            TEXT NOT NULL DEFAULT 'valid',
    embedding           BLOB DEFAULT NULL,
    tags                TEXT NOT NULL DEFAULT '[]',
    cross_refs          TEXT NOT NULL DEFAULT '[]',
    provenance          TEXT NOT NULL DEFAULT '{}',
    metadata            TEXT NOT NULL DEFAULT '{}',
    created_at          TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    updated_at          TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
"""

def _knowledge_indexes(table: str) -> list[str]:
    return [
        f"CREATE INDEX IF NOT EXISTS idx_{table}_fingerprint ON {table}(fingerprint);",
        f"CREATE UNIQUE INDEX IF NOT EXISTS uq_{table}_fingerprint ON {table}(fingerprint);",
        f"CREATE INDEX IF NOT EXISTS idx_{table}_domain ON {table}(domain);",
        f"CREATE INDEX IF NOT EXISTS idx_{table}_timestamp ON {table}(timestamp DESC);",
        f"CREATE INDEX IF NOT EXISTS idx_{table}_valid_salience ON {table}(validity, salience DESC);"
    ]

def get_conn() -> sqlite3.Connection:
    if not hasattr(_local, "conn"):
        DB_PATH.parent.mkdir(parents=True, exist_ok=True)
        # Handle Windows paths safely by converting to absolute string
        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  # Graceful fallback if vector search is unavailable
            
        conn.execute("PRAGMA journal_mode=WAL;")
        conn.execute("PRAGMA synchronous=NORMAL;")
        conn.execute("PRAGMA busy_timeout=30000;")  # 30s timeout for massive concurrency
        conn.create_function("SQRT", 1, math.sqrt)
        conn.row_factory = sqlite3.Row
        _local.conn = conn
    return _local.conn

def init_db() -> None:
    conn = get_conn()
    tables = [
        "lessons", "failures", "patterns", "private", "intents", "decisions", "facts", "evidence"
    ]
    with conn:
        for t in tables:
            conn.execute(f"CREATE TABLE IF NOT EXISTS {t} ({_KNOWLEDGE_COLS});")
            for idx in _knowledge_indexes(t):
                conn.execute(idx)

        # Initialize vector FTS lookup if supported
        conn.execute("""
        CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_fts USING fts5(
            item_id UNINDEXED, content, title, domain, agent, table_name
        );
        """)

        # Special operational tables (Graph Nodes, Edges, Schemas, Procedures)
        conn.execute("""
        CREATE TABLE IF NOT EXISTS graph_nodes (
            id TEXT PRIMARY KEY, type TEXT NOT NULL DEFAULT 'CONCEPT',
            text TEXT NOT NULL, source TEXT DEFAULT '', confidence REAL DEFAULT 0.5,
            domain TEXT NOT NULL DEFAULT 'general',
            date TEXT DEFAULT '', degree INTEGER DEFAULT 0, embedding BLOB DEFAULT NULL,
            metadata TEXT DEFAULT '{}'
        );""")
        conn.execute("""
        CREATE TABLE IF NOT EXISTS graph_edges (
            id INTEGER PRIMARY KEY AUTOINCREMENT, from_node TEXT NOT NULL,
            to_node TEXT NOT NULL, edge_type TEXT NOT NULL,
            confidence REAL DEFAULT 0.5, source_node TEXT DEFAULT '',
            metadata TEXT DEFAULT '{}',
            UNIQUE(from_node, edge_type, to_node)
        );""")

        # Migration: add domain column to existing graph_nodes tables
        try:
            conn.execute("ALTER TABLE graph_nodes ADD COLUMN domain TEXT NOT NULL DEFAULT 'general'")
        except sqlite3.OperationalError:
            pass  # Column already exists

def insert_knowledge(table: str, data: Dict[str, Any]) -> str:
    """Insert knowledge into the designated table using UNIQUE constraint for idempotency."""
    conn = get_conn()
    content = data.get("content", "")
    fingerprint = hashlib.md5(content[:120].encode('utf-8')).hexdigest() if content else "null"
    
    # Defaults and coercion
    item_id = data.get("id") or f"L-{fingerprint[:8]}"
    tags = json.dumps(data.get("tags", []))
    meta = json.dumps(data.get("metadata", {}))
    
    with conn:
        conn.execute(f"""
            INSERT OR IGNORE INTO {table} 
            (id, fingerprint, type, content, title, domain, agent, timestamp, salience, tags, metadata)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            item_id, fingerprint, 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)), tags, meta
        ))
        
        # Dual-write into FTS5 immediately
        # Using item_id to reference the source row instead of last_insert_rowid() which may be stale on IGNORE.
        if conn.execute("SELECT changes()").fetchone()[0] > 0:
            conn.execute("""
                INSERT INTO knowledge_fts (item_id, content, title, domain, agent, table_name)
                VALUES (?, ?, ?, ?, ?, ?)
            """, (item_id, content, data.get("title", ""), data.get("domain", "general"), data.get("agent", "unknown"), table))
        
    return item_id

def search(query: str, limit: int = 5) -> List[Dict[str, Any]]:
    """FTS5 text search bridging the SQLite layer."""
    conn = get_conn()
    cursor = conn.execute("""
        SELECT content, title, domain, agent, table_name, rank 
        FROM knowledge_fts 
        WHERE knowledge_fts MATCH ? 
        ORDER BY rank 
        LIMIT ?
    """, (query, limit))
    return [dict(row) for row in cursor.fetchall()]
