"""brain_db_schema.py — Schema constants, DDL helpers, and init_db.

Extracted from brain_db.py (split P47.4).
Runtime ops (get_conn, insert_knowledge, upsert_knowledge, bulk_upsert, search)
remain in brain_db.py, which re-exports everything defined here for backward
compatibility.
"""
import sqlite3

_VALID_TABLES = frozenset({
    "lessons", "failures", "patterns", "private", "intents", "decisions",
    "facts", "evidence", "predictions", "knowledge_fts",
    "schemas", "anti_patterns", "procedures", "episodic",
    "sessions", "session_turns", "retrieval_metrics",
    "graph_nodes", "graph_edges",
})


def _validate_table(table: str) -> str:
    if table not in _VALID_TABLES:
        raise ValueError(f"Invalid table name: {table!r}")
    return table


_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,
    last_reinforced     TEXT DEFAULT NULL,
    source_type         TEXT NOT NULL DEFAULT 'CONVERSATIONAL',
    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:
    _validate_table(table)
    return [
        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 _tables_exist(conn) -> bool:
    """Quick read-only check if all required tables exist (no write lock)."""
    try:
        _REQUIRED = {
            "lessons", "failures", "patterns", "private", "intents",
            "decisions", "facts", "evidence", "predictions",
            "knowledge_fts", "retrieval_metrics", "graph_nodes",
            "graph_edges", "schemas", "anti_patterns", "procedures", "episodic",
            "sessions", "session_turns",
        }
        rows = conn.execute(
            "SELECT name FROM sqlite_master WHERE type IN ('table','view')"
        ).fetchall()
        existing = {r[0] for r in rows}
        return _REQUIRED.issubset(existing)
    except Exception:
        return False


def init_db(conn) -> None:
    """Create all tables and indexes. Caller passes the thread-local connection."""
    tables = [
        "lessons", "failures", "patterns", "private", "intents", "decisions", "facts", "evidence"
    ]
    with conn:
        # predictions table (queried by Gate 3 and prediction_daemon)
        conn.execute("""CREATE TABLE IF NOT EXISTS predictions (
            id TEXT PRIMARY KEY, content TEXT NOT NULL,
            domain TEXT NOT NULL DEFAULT 'general', agent TEXT NOT NULL DEFAULT 'unknown',
            confidence REAL NOT NULL DEFAULT 0.5, salience REAL NOT NULL DEFAULT 1.0,
            timestamp TEXT NOT NULL DEFAULT '', outcome TEXT DEFAULT NULL,
            verified INTEGER DEFAULT 0, metadata TEXT DEFAULT '{}'
        );""")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_pred_domain ON predictions(domain);")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_pred_ts ON predictions(timestamp DESC);")
        for t in tables:
            conn.execute(f"CREATE TABLE IF NOT EXISTS {t} ({_KNOWLEDGE_COLS});")
            for idx in _knowledge_indexes(t):
                conn.execute(idx)

        conn.execute("""
        CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_fts USING fts5(
            item_id UNINDEXED, content, title, domain, agent, table_name
        );
        """)

        # FTS auto-sync triggers for knowledge tables
        for _tbl in tables:
            conn.execute(f"""
            CREATE TRIGGER IF NOT EXISTS fts_insert_{_tbl} AFTER INSERT ON {_tbl} BEGIN
                INSERT INTO knowledge_fts(item_id, content, title, domain, agent, table_name)
                VALUES (NEW.id, NEW.content, NEW.title, NEW.domain, NEW.agent, '{_tbl}');
            END;""")
            conn.execute(f"""
            CREATE TRIGGER IF NOT EXISTS fts_delete_{_tbl} AFTER DELETE ON {_tbl} BEGIN
                DELETE FROM knowledge_fts WHERE item_id = OLD.id AND table_name = '{_tbl}';
            END;""")
            conn.execute(f"""
            CREATE TRIGGER IF NOT EXISTS fts_update_{_tbl} AFTER UPDATE OF content, title, domain ON {_tbl} BEGIN
                DELETE FROM knowledge_fts WHERE item_id = OLD.id AND table_name = '{_tbl}';
                INSERT INTO knowledge_fts(item_id, content, title, domain, agent, table_name)
                VALUES (NEW.id, NEW.content, NEW.title, NEW.domain, NEW.agent, '{_tbl}');
            END;""")

        conn.execute("""
        CREATE TABLE IF NOT EXISTS retrieval_metrics (
            item_key TEXT PRIMARY KEY,
            retrieval_count INTEGER DEFAULT 0,
            last_accessed TEXT
        );""")

        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)
        );""")

        conn.execute("CREATE INDEX IF NOT EXISTS idx_ge_to_node ON graph_edges(to_node);")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_gn_degree ON graph_nodes(degree DESC);")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_gn_domain ON graph_nodes(domain);")

        conn.execute("""
        CREATE TABLE IF NOT EXISTS schemas (
            id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT,
            domain TEXT DEFAULT 'general', confidence REAL DEFAULT 0.5,
            type TEXT DEFAULT 'heuristic', source TEXT DEFAULT 'schema_daemon',
            evidence_links TEXT DEFAULT '[]', created TEXT, metadata TEXT DEFAULT '{}'
        );""")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_schemas_domain ON schemas(domain);")
        conn.execute("""
        CREATE TABLE IF NOT EXISTS anti_patterns (
            id TEXT PRIMARY KEY, description TEXT NOT NULL, rule TEXT,
            reason TEXT, domain TEXT DEFAULT 'general', keywords TEXT DEFAULT '[]',
            severity TEXT DEFAULT 'medium', evidence_count INTEGER DEFAULT 1,
            status TEXT DEFAULT 'active', source TEXT, created TEXT,
            metadata TEXT DEFAULT '{}',
            validity TEXT DEFAULT 'valid',
            access_count INTEGER DEFAULT 0
        );""")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_ap_status ON anti_patterns(status);")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_ap_domain ON anti_patterns(domain);")
        conn.execute("""
        CREATE TABLE IF NOT EXISTS procedures (
            id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT,
            steps TEXT DEFAULT '[]', domain TEXT DEFAULT 'general',
            confidence REAL DEFAULT 0.5, executions INTEGER DEFAULT 0,
            successes INTEGER DEFAULT 0, failures_count INTEGER DEFAULT 0,
            source TEXT DEFAULT 'procedure_daemon', created TEXT,
            metadata TEXT DEFAULT '{}'
        );""")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_proc_domain ON procedures(domain);")
        conn.execute("""
        CREATE TABLE IF NOT EXISTS episodic (
            id TEXT PRIMARY KEY, summary TEXT, entities TEXT DEFAULT '[]',
            agent TEXT, domain TEXT DEFAULT 'general', keywords TEXT DEFAULT '[]',
            timestamp TEXT, session_id TEXT, metadata TEXT DEFAULT '{}'
        );""")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_episodic_ts ON episodic(timestamp DESC);")
        # P2-4 fix: episodic.confidence column (was only added at runtime by episodic_writer)
        try: conn.execute("ALTER TABLE episodic ADD COLUMN confidence REAL DEFAULT 0.85")
        except sqlite3.OperationalError: pass

        # --- Cross-session tables (P47) ---
        conn.execute("""
        CREATE TABLE IF NOT EXISTS sessions (
            id TEXT PRIMARY KEY,
            agent TEXT NOT NULL,
            started_at TEXT NOT NULL,
            ended_at TEXT,
            turn_count INTEGER DEFAULT 0,
            summary TEXT DEFAULT '',
            user_intent TEXT DEFAULT '',
            key_decisions TEXT DEFAULT '[]',
            key_discoveries TEXT DEFAULT '[]',
            files_touched TEXT DEFAULT '[]',
            predecessor_session TEXT,
            domain TEXT DEFAULT 'general',
            status TEXT DEFAULT 'active',
            metadata TEXT DEFAULT '{}'
        );""")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_sessions_agent_time ON sessions(agent, started_at DESC);")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);")

        conn.execute("""
        CREATE TABLE IF NOT EXISTS session_turns (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            session_id TEXT NOT NULL,
            turn_index INTEGER NOT NULL,
            role TEXT NOT NULL,
            content TEXT NOT NULL,
            content_len INTEGER DEFAULT 0,
            timestamp TEXT NOT NULL,
            domain TEXT DEFAULT 'general',
            metadata TEXT DEFAULT '{}'
        );""")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_turns_session ON session_turns(session_id, turn_index);")
        conn.execute("CREATE INDEX IF NOT EXISTS idx_turns_ts ON session_turns(timestamp DESC);")

        for _stmt in [
            "ALTER TABLE graph_nodes ADD COLUMN domain TEXT NOT NULL DEFAULT 'general'",
            "ALTER TABLE anti_patterns ADD COLUMN validity TEXT DEFAULT 'valid'",
            "ALTER TABLE anti_patterns ADD COLUMN access_count INTEGER DEFAULT 0",
            "ALTER TABLE anti_patterns ADD COLUMN confidence REAL DEFAULT 0.5",
        ] + [f"ALTER TABLE {t} ADD COLUMN last_reinforced TEXT DEFAULT NULL" for t in tables]:
            try: conn.execute(_stmt)
            except sqlite3.OperationalError: pass
        conn.execute("UPDATE anti_patterns SET confidence=0.85 WHERE evidence_count>=5 AND (confidence IS NULL OR confidence<0.85)")

        for t in tables + ["schemas", "anti_patterns"]:
            try:
                conn.execute(
                    "ALTER TABLE " + t + " ADD COLUMN"
                    " source_type TEXT NOT NULL DEFAULT 'CONVERSATIONAL'"
                )
            except sqlite3.OperationalError:
                pass
            try:
                conn.execute(
                    "CREATE INDEX IF NOT EXISTS idx_" + t + "_source_type"
                    " ON " + t + "(source_type)"
                )
            except Exception:
                pass

        _FTS_MAP = {"schemas": ("description","name","source"), "anti_patterns": ("description","rule","source")}
        for _tbl in ("intents", "anti_patterns", "decisions", "schemas"):
            try:
                if conn.execute(f"SELECT COUNT(*) FROM {_tbl}").fetchone()[0] > 0 and conn.execute("SELECT COUNT(*) FROM knowledge_fts WHERE table_name=?", (_tbl,)).fetchone()[0] == 0:
                    _c, _t, _a = _FTS_MAP.get(_tbl, ("content","title","agent"))
                    conn.execute(f"INSERT INTO knowledge_fts (item_id,content,title,domain,agent,table_name) SELECT id,{_c},{_t},domain,{_a},'{_tbl}' FROM {_tbl}")
            except Exception: pass

        for _tbl in ("lessons","failures","patterns","facts","intents","decisions","schemas","anti_patterns"):
            try: conn.execute(f"DELETE FROM knowledge_fts WHERE table_name=? AND item_id NOT IN (SELECT id FROM {_tbl})", (_tbl,))
            except Exception: pass

        # Item 26: Primacy protection — earliest items are founding episodes, mark permanent
        try:
            for table in tables:
                conn.execute(
                    f"UPDATE {table} SET validity = 'permanent' "
                    f"WHERE id IN (SELECT id FROM {table} ORDER BY timestamp ASC LIMIT 10) "
                    f"AND validity != 'permanent'"
                )
            conn.commit()
        except Exception:
            pass
