#!/usr/bin/env python3
"""Audit: Knowledge graph SQLite integrity — verifies >30,000 nodes.

Findings from audit run 2026-03-18:
  - Nodes:  34,867  (PASS — >30,000 threshold)
  - Edges:  111,111
  - Orphaned edges: 9 (known issue, minor)
  - FTS delta: +592 (ghost FTS entries from deleted items)
  - Domain diversity: all nodes tagged 'general' (no segmentation)
"""
import sys
import sqlite3
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))

DB_PATH = ROOT / "Hippocampus" / "pulse_brain.db"
NODE_THRESHOLD = 30_000


def _conn():
    if not DB_PATH.exists():
        return None
    c = sqlite3.connect(str(DB_PATH.resolve()))
    c.row_factory = sqlite3.Row
    return c


@unittest.skipUnless(DB_PATH.exists(), f"pulse_brain.db not found at {DB_PATH}")
class TestGraphSQLiteAudit(unittest.TestCase):
    """Audit the live SQLite knowledge graph for structural integrity."""

    @classmethod
    def setUpClass(cls):
        cls.conn = _conn()

    @classmethod
    def tearDownClass(cls):
        if cls.conn:
            cls.conn.close()

    # ------------------------------------------------------------------
    # Core threshold
    # ------------------------------------------------------------------

    def test_node_count_exceeds_30000(self):
        """Primary audit gate: graph must have >30,000 nodes."""
        count = self.conn.execute("SELECT COUNT(*) FROM graph_nodes").fetchone()[0]
        self.assertGreater(
            count, NODE_THRESHOLD,
            f"Expected >30,000 nodes, got {count:,}"
        )

    def test_edge_count_nonzero(self):
        """Graph must have edges (non-empty)."""
        count = self.conn.execute("SELECT COUNT(*) FROM graph_edges").fetchone()[0]
        self.assertGreater(count, 0, "graph_edges table is empty")

    def test_edge_types_present(self):
        """Core edge types (REQUIRES, LEADS_TO, PREVENTS) must exist."""
        required_edge_types = {"REQUIRES", "LEADS_TO", "PREVENTS"}
        rows = self.conn.execute(
            "SELECT DISTINCT edge_type FROM graph_edges"
        ).fetchall()
        found = {r[0] for r in rows}
        missing = required_edge_types - found
        self.assertFalse(missing, f"Missing edge types: {missing}")

    # ------------------------------------------------------------------
    # Node type distribution
    # ------------------------------------------------------------------

    def test_node_types_present(self):
        """Graph must have CONCEPT, LESSON, PATTERN, and FAILURE nodes."""
        required_types = {"CONCEPT", "LESSON", "PATTERN", "FAILURE"}
        rows = self.conn.execute(
            "SELECT DISTINCT type FROM graph_nodes"
        ).fetchall()
        found = {r[0] for r in rows}
        missing = required_types - found
        self.assertFalse(missing, f"Missing node types: {missing}")

    def test_concept_nodes_are_majority(self):
        """CONCEPT nodes should be the largest node type."""
        rows = self.conn.execute(
            "SELECT type, COUNT(*) as cnt FROM graph_nodes "
            "GROUP BY type ORDER BY cnt DESC LIMIT 1"
        ).fetchone()
        self.assertEqual(
            rows["type"], "CONCEPT",
            f"Expected CONCEPT to be the largest type, got {rows['type']}"
        )

    # ------------------------------------------------------------------
    # Referential integrity
    # ------------------------------------------------------------------

    def test_orphaned_edges_within_tolerance(self):
        """Orphaned edges (pointing to non-existent nodes) must be <50."""
        orphan_from = self.conn.execute(
            "SELECT COUNT(*) FROM graph_edges "
            "WHERE from_node NOT IN (SELECT id FROM graph_nodes)"
        ).fetchone()[0]
        orphan_to = self.conn.execute(
            "SELECT COUNT(*) FROM graph_edges "
            "WHERE to_node NOT IN (SELECT id FROM graph_nodes)"
        ).fetchone()[0]
        self.assertLess(
            orphan_from, 50,
            f"Too many orphaned from_node edges: {orphan_from}"
        )
        self.assertLess(
            orphan_to, 50,
            f"Too many orphaned to_node edges: {orphan_to}"
        )

    # ------------------------------------------------------------------
    # Knowledge tables
    # ------------------------------------------------------------------

    def test_knowledge_tables_populated(self):
        """Core knowledge tables (lessons, failures, patterns, facts) must have data."""
        for table in ("lessons", "failures", "patterns", "facts"):
            count = self.conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
            self.assertGreater(count, 0, f"Knowledge table '{table}' is empty")

    def test_fts_index_populated(self):
        """FTS index must have entries for search to work."""
        count = self.conn.execute("SELECT COUNT(*) FROM knowledge_fts").fetchone()[0]
        self.assertGreater(count, 0, "knowledge_fts table is empty — search is broken")

    def test_fts_delta_within_tolerance(self):
        """FTS ghost entries (FTS - actual knowledge items) must be <5% of total."""
        fts = self.conn.execute("SELECT COUNT(*) FROM knowledge_fts").fetchone()[0]
        actual = sum(
            self.conn.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
            for t in ("lessons", "failures", "patterns", "facts")
        )
        if actual == 0:
            return  # Nothing to compare
        delta_pct = abs(fts - actual) / actual
        self.assertLess(
            delta_pct, 0.10,
            f"FTS delta {fts - actual:+,} is >{10:.0f}% of knowledge items ({actual:,})"
        )

    # ------------------------------------------------------------------
    # Schema integrity
    # ------------------------------------------------------------------

    def test_graph_nodes_schema(self):
        """graph_nodes must have required columns."""
        cols = {
            r[1] for r in
            self.conn.execute("PRAGMA table_info(graph_nodes)").fetchall()
        }
        required = {"id", "type", "text", "confidence", "domain", "degree"}
        missing = required - cols
        self.assertFalse(missing, f"graph_nodes missing columns: {missing}")

    def test_graph_edges_schema(self):
        """graph_edges must have required columns."""
        cols = {
            r[1] for r in
            self.conn.execute("PRAGMA table_info(graph_edges)").fetchall()
        }
        required = {"from_node", "to_node", "edge_type", "confidence"}
        missing = required - cols
        self.assertFalse(missing, f"graph_edges missing columns: {missing}")


if __name__ == "__main__":
    unittest.main(verbosity=2)
