"""SecureLLM Audit Logger.

Lightweight module for writing security events to the ``_security_audit`` table.
Designed for use by Claude Code hooks — **fail-silent** so it never blocks tool
execution.  Every public function swallows exceptions and returns a safe default
(empty list, empty dict, or ``None``).

Table schema (``_security_audit``):

    ============== ======= ======================================================
    Column         Type    Description
    ============== ======= ======================================================
    id             INTEGER Auto-incrementing primary key
    event_type     TEXT    ``"detection"`` or ``"policy"``
    hook_phase     TEXT    ``"pre"`` or ``"post"``
    tool_name      TEXT    MCP tool that triggered the event (e.g. ``"Bash"``)
    severity       TEXT    ``"info"``, ``"high"``, or ``"critical"``
    pattern_name   TEXT    Matched secret/PII pattern name (detections only)
    confidence     REAL    Detection confidence score 0.0–1.0
    outcome        TEXT    ``"allow"``, ``"blocked"``, ``"refuse"``, ``"escalate"``
    rule_id        TEXT    Policy rule identifier (policy decisions only)
    reason         TEXT    Human-readable reason for the decision
    content_hash   TEXT    Truncated SHA-256 of matched content (no raw secrets)
    session_id     TEXT    Claude session identifier for correlation
    node_id        TEXT    Cluster node identifier (reserved for multi-node)
    created_at     TEXT    ISO 8601 timestamp (auto-populated by SQLite)
    ============== ======= ======================================================

Usage::

    from greymatter_core.security_audit import log_detection, log_policy_decision

    # Record a secret detection
    log_detection(
        hook_phase="pre",
        tool_name="Bash",
        pattern_name="aws_access_key",
        severity="critical",
        confidence=0.95,
        outcome="blocked",
        session_id="abc123",
    )

    # Record a policy engine decision
    log_policy_decision(
        hook_phase="pre",
        tool_name="Write",
        rule_id="r-block-secrets",
        outcome="refuse",
        reason="Content contains AWS credentials",
    )

    # Query recent events for dashboards
    from greymatter_core.security_audit import get_recent_events, get_stats

    events = get_recent_events(limit=20, severity="critical")
    stats = get_stats()  # aggregate counts by severity, outcome, pattern
"""
import hashlib
import os
import sqlite3
from pathlib import Path


def _get_db_path() -> Path:
    """Resolve the GreyMatter database path.

    Checks ``GM_DB_PATH`` environment variable first, falls back to
    ``~/.greymatter/greymatter.db``.
    """
    db_path = os.environ.get("GM_DB_PATH")
    if db_path:
        return Path(db_path)
    return Path.home() / ".greymatter" / "greymatter.db"


def _ensure_table(conn: sqlite3.Connection) -> None:
    """Create the audit table if it does not exist (idempotent)."""
    conn.execute("""
        CREATE TABLE IF NOT EXISTS _security_audit (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            event_type TEXT NOT NULL,
            hook_phase TEXT NOT NULL,
            tool_name TEXT NOT NULL DEFAULT '',
            severity TEXT NOT NULL DEFAULT 'info',
            pattern_name TEXT DEFAULT NULL,
            confidence REAL DEFAULT NULL,
            outcome TEXT NOT NULL DEFAULT 'allow',
            rule_id TEXT DEFAULT NULL,
            reason TEXT DEFAULT NULL,
            content_hash TEXT DEFAULT NULL,
            session_id TEXT DEFAULT NULL,
            node_id TEXT DEFAULT NULL,
            created_at TEXT DEFAULT (datetime('now'))
        )
    """)


def _content_hash(content: str) -> str:
    """SHA-256 hash of content for correlation without storing raw secrets."""
    return hashlib.sha256(content.encode()).hexdigest()[:16]


def _write_event(event: dict) -> None:
    """Write a single audit event to the ``_security_audit`` table.

    Opens a fresh connection, ensures the table exists, inserts the row,
    and closes the connection.  All exceptions are swallowed — this function
    must never raise, because it runs inside Claude Code pre/post hooks
    where an unhandled error would block the user's tool execution.
    """
    try:
        db_path = _get_db_path()
        if not db_path.parent.exists():
            db_path.parent.mkdir(parents=True, exist_ok=True)

        conn = sqlite3.connect(str(db_path), timeout=2.0)
        try:
            _ensure_table(conn)
            conn.execute(
                "INSERT INTO _security_audit "
                "(event_type, hook_phase, tool_name, severity, pattern_name, "
                " confidence, outcome, rule_id, reason, content_hash, "
                " session_id, node_id) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                (
                    event.get("event_type", "unknown"),
                    event.get("hook_phase", "unknown"),
                    event.get("tool_name", ""),
                    event.get("severity", "info"),
                    event.get("pattern_name"),
                    event.get("confidence"),
                    event.get("outcome", "allow"),
                    event.get("rule_id"),
                    event.get("reason"),
                    event.get("content_hash"),
                    event.get("session_id"),
                    event.get("node_id"),
                ),
            )
            conn.commit()
        finally:
            conn.close()
    except Exception:
        pass  # fail-silent — never break the hook


def log_detection(
    hook_phase: str,
    tool_name: str,
    pattern_name: str,
    severity: str,
    confidence: float,
    outcome: str = "allow",
    content: str = "",
    session_id: str = "",
) -> None:
    """Log a secret or PII detection event.

    Args:
        hook_phase: ``"pre"`` or ``"post"`` — when in the hook lifecycle
            the detection occurred.
        tool_name: MCP tool name that triggered the scan (e.g. ``"Bash"``).
        pattern_name: Identifier of the matched pattern (e.g. ``"aws_access_key"``).
        severity: ``"info"``, ``"high"``, or ``"critical"``.
        confidence: Detection confidence score between 0.0 and 1.0.
        outcome: Action taken — ``"allow"``, ``"blocked"``, or ``"redact"``.
        content: Raw matched content (stored as a truncated SHA-256 hash,
            never in plaintext).
        session_id: Claude session ID for event correlation.  Falls back to
            the ``CLAUDE_SESSION_ID`` environment variable if empty.
    """
    _write_event({
        "event_type": "detection",
        "hook_phase": hook_phase,
        "tool_name": tool_name,
        "pattern_name": pattern_name,
        "severity": severity,
        "confidence": confidence,
        "outcome": outcome,
        "content_hash": _content_hash(content) if content else None,
        "session_id": session_id or os.environ.get("CLAUDE_SESSION_ID", ""),
    })


def log_policy_decision(
    hook_phase: str,
    tool_name: str,
    rule_id: str,
    outcome: str,
    reason: str = "",
    content: str = "",
    session_id: str = "",
) -> None:
    """Log a policy engine decision.

    Severity is derived from the outcome: ``"refuse"`` and ``"escalate"``
    map to ``"high"``; all others default to ``"info"``.

    Args:
        hook_phase: ``"pre"`` or ``"post"`` — when in the hook lifecycle.
        tool_name: MCP tool name that was evaluated.
        rule_id: Identifier of the policy rule that matched.
        outcome: Decision — ``"allow"``, ``"refuse"``, ``"escalate"``, or
            ``"redact"``.
        reason: Human-readable explanation for the decision.
        content: Content that was evaluated (stored as truncated SHA-256).
        session_id: Claude session ID.  Falls back to ``CLAUDE_SESSION_ID``
            env var if empty.
    """
    _write_event({
        "event_type": "policy",
        "hook_phase": hook_phase,
        "tool_name": tool_name,
        "severity": "high" if outcome in ("refuse", "escalate") else "info",
        "outcome": outcome,
        "rule_id": rule_id,
        "reason": reason,
        "content_hash": _content_hash(content) if content else None,
        "session_id": session_id or os.environ.get("CLAUDE_SESSION_ID", ""),
    })


def get_recent_events(limit: int = 50, severity: str = "") -> list[dict]:
    """Query recent audit events, ordered newest-first.

    Intended for dashboards, reports, and incident investigation.
    Returns an empty list on any error (missing DB, missing table, etc.).

    Args:
        limit: Maximum number of events to return (default 50).
        severity: If non-empty, filter to events matching this severity level.

    Returns:
        List of event dicts with all ``_security_audit`` columns.
    """
    try:
        db_path = _get_db_path()
        if not db_path.exists():
            return []

        conn = sqlite3.connect(str(db_path), timeout=2.0)
        conn.row_factory = sqlite3.Row
        try:
            if severity:
                rows = conn.execute(
                    "SELECT * FROM _security_audit WHERE severity = ? "
                    "ORDER BY created_at DESC LIMIT ?",
                    (severity, limit),
                ).fetchall()
            else:
                rows = conn.execute(
                    "SELECT * FROM _security_audit "
                    "ORDER BY created_at DESC LIMIT ?",
                    (limit,),
                ).fetchall()
            return [dict(r) for r in rows]
        finally:
            conn.close()
    except Exception:
        return []


def get_stats() -> dict:
    """Get aggregate statistics for the security dashboard.

    Returns an empty dict on any error.

    Returns:
        Dict with keys:
        - ``total_events`` (int): Total audit events recorded.
        - ``blocked_count`` (int): Events with ``outcome='blocked'``.
        - ``by_severity`` (dict[str, int]): Event counts grouped by severity.
        - ``top_patterns`` (list[dict]): Top 10 detection patterns by frequency,
          each with ``pattern`` (str) and ``count`` (int).
    """
    try:
        db_path = _get_db_path()
        if not db_path.exists():
            return {}

        conn = sqlite3.connect(str(db_path), timeout=2.0)
        try:
            total = conn.execute(
                "SELECT COUNT(*) FROM _security_audit"
            ).fetchone()[0]

            blocked = conn.execute(
                "SELECT COUNT(*) FROM _security_audit WHERE outcome = 'blocked'"
            ).fetchone()[0]

            by_severity = {}
            for row in conn.execute(
                "SELECT severity, COUNT(*) as cnt FROM _security_audit GROUP BY severity"
            ).fetchall():
                by_severity[row[0]] = row[1]

            top_patterns = []
            for row in conn.execute(
                "SELECT pattern_name, COUNT(*) as cnt FROM _security_audit "
                "WHERE pattern_name IS NOT NULL "
                "GROUP BY pattern_name ORDER BY cnt DESC LIMIT 10"
            ).fetchall():
                top_patterns.append({"pattern": row[0], "count": row[1]})

            return {
                "total_events": total,
                "blocked_count": blocked,
                "by_severity": by_severity,
                "top_patterns": top_patterns,
            }
        finally:
            conn.close()
    except Exception:
        return {}
