from __future__ import annotations

import json
import sqlite3
import uuid
from pathlib import Path
from typing import Any

from armorer.config import resolve_armorer_config_dir


SCHEMA_VERSION = 1


def resolve_db_path(config_dir: Path | None = None) -> Path:
    root = resolve_armorer_config_dir(config_dir)
    root.mkdir(parents=True, exist_ok=True)
    return root / "armorer.db"


def get_connection(config_dir: Path | None = None) -> sqlite3.Connection:
    path = resolve_db_path(config_dir)
    conn = sqlite3.connect(path)
    conn.row_factory = sqlite3.Row
    _ensure_schema(conn)
    return conn


def _ensure_schema(conn: sqlite3.Connection) -> None:
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS schema_migrations (
            version INTEGER PRIMARY KEY,
            applied_at REAL NOT NULL DEFAULT (unixepoch())
        )
        """
    )
    applied = {
        int(row["version"])
        for row in conn.execute("SELECT version FROM schema_migrations").fetchall()
    }
    if 1 not in applied:
        conn.executescript(
            """
            CREATE TABLE IF NOT EXISTS security_alerts (
                id TEXT PRIMARY KEY,
                ts REAL NOT NULL,
                created_at REAL NOT NULL DEFAULT (unixepoch()),
                agent TEXT NOT NULL,
                conversation_id TEXT NOT NULL,
                source TEXT NOT NULL,
                source_family TEXT NOT NULL,
                severity TEXT NOT NULL,
                severity_rank INTEGER NOT NULL,
                confidence REAL NOT NULL DEFAULT 0.0,
                flags_json TEXT NOT NULL,
                primary_flag TEXT NOT NULL,
                reasons_json TEXT NOT NULL,
                input_preview TEXT NOT NULL,
                output_preview TEXT NOT NULL,
                incident_marker TEXT NOT NULL,
                status TEXT NOT NULL DEFAULT 'open',
                dedupe_fingerprint TEXT NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_security_alerts_status_ts
                ON security_alerts(status, ts DESC);
            CREATE INDEX IF NOT EXISTS idx_security_alerts_agent_ts
                ON security_alerts(agent, ts DESC);
            CREATE INDEX IF NOT EXISTS idx_security_alerts_severity_ts
                ON security_alerts(severity_rank DESC, ts DESC);
            CREATE INDEX IF NOT EXISTS idx_security_alerts_fingerprint
                ON security_alerts(dedupe_fingerprint, ts DESC);

            CREATE TABLE IF NOT EXISTS runtime_events (
                id TEXT PRIMARY KEY,
                ts REAL NOT NULL,
                created_at REAL NOT NULL DEFAULT (unixepoch()),
                agent_alias TEXT NOT NULL,
                agent_id TEXT NOT NULL DEFAULT '',
                event_type TEXT NOT NULL,
                subtype TEXT NOT NULL DEFAULT '',
                severity TEXT NOT NULL DEFAULT 'info',
                path TEXT NOT NULL DEFAULT '',
                dst_host TEXT NOT NULL DEFAULT '',
                dst_ip TEXT NOT NULL DEFAULT '',
                dst_port INTEGER,
                verdict TEXT NOT NULL DEFAULT '',
                process_name TEXT NOT NULL DEFAULT '',
                container_id TEXT NOT NULL DEFAULT '',
                raw_json TEXT NOT NULL DEFAULT '{}'
            );
            CREATE INDEX IF NOT EXISTS idx_runtime_events_agent_ts
                ON runtime_events(agent_alias, ts DESC);
            CREATE INDEX IF NOT EXISTS idx_runtime_events_type_ts
                ON runtime_events(event_type, ts DESC);
            """
        )
        conn.execute(
            "INSERT OR IGNORE INTO schema_migrations(version) VALUES (?)",
            (1,),
        )
        conn.commit()


def insert_security_alert(config_dir: Path | None = None, record: dict[str, Any] | None = None) -> dict[str, Any]:
    payload = dict(record or {})
    payload.setdefault("id", str(uuid.uuid4()))
    payload.setdefault("ts", 0.0)
    payload.setdefault("agent", "")
    payload.setdefault("conversation_id", "")
    payload.setdefault("source", "")
    payload.setdefault("source_family", "")
    payload.setdefault("severity", "LOW")
    payload.setdefault("severity_rank", 1)
    payload.setdefault("confidence", 0.0)
    payload.setdefault("flags", [])
    payload.setdefault("primary_flag", "Suspicious behavior")
    payload.setdefault("reasons", [])
    payload.setdefault("input_preview", "")
    payload.setdefault("output_preview", "")
    payload.setdefault("incident_marker", "")
    payload.setdefault("status", "open")
    payload.setdefault("dedupe_fingerprint", "")
    with get_connection(config_dir) as conn:
        conn.execute(
            """
            INSERT INTO security_alerts (
                id, ts, agent, conversation_id, source, source_family,
                severity, severity_rank, confidence, flags_json, primary_flag,
                reasons_json, input_preview, output_preview, incident_marker,
                status, dedupe_fingerprint
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                payload["id"],
                float(payload["ts"]),
                str(payload["agent"]),
                str(payload["conversation_id"]),
                str(payload["source"]),
                str(payload["source_family"]),
                str(payload["severity"]),
                int(payload["severity_rank"]),
                float(payload["confidence"]),
                json.dumps(list(payload["flags"]), ensure_ascii=True),
                str(payload["primary_flag"]),
                json.dumps(list(payload["reasons"]), ensure_ascii=True),
                str(payload["input_preview"]),
                str(payload["output_preview"]),
                str(payload["incident_marker"]),
                str(payload["status"]),
                str(payload["dedupe_fingerprint"]),
            ),
        )
        conn.commit()
    return payload


def list_security_alerts(
    config_dir: Path | None = None,
    *,
    limit: int = 50,
    sort: str = "criticality",
    status: str = "",
    agent: str = "",
) -> list[dict[str, Any]]:
    clauses: list[str] = []
    params: list[Any] = []
    if status:
        clauses.append("status = ?")
        params.append(status)
    if agent:
        clauses.append("agent = ?")
        params.append(agent)
    where_sql = f"WHERE {' AND '.join(clauses)}" if clauses else ""
    order_sql = "ORDER BY severity_rank DESC, ts DESC"
    if str(sort).strip().lower() == "time":
        order_sql = "ORDER BY ts DESC"
    query = f"""
        SELECT id, ts, agent, conversation_id, source, source_family, severity,
               severity_rank, confidence, flags_json, primary_flag, reasons_json,
               input_preview, output_preview, incident_marker, status, dedupe_fingerprint
        FROM security_alerts
        {where_sql}
        {order_sql}
        LIMIT ?
    """
    params.append(max(1, int(limit)))
    with get_connection(config_dir) as conn:
        rows = conn.execute(query, params).fetchall()
    items: list[dict[str, Any]] = []
    for row in rows:
        items.append(
            {
                "id": row["id"],
                "ts": row["ts"],
                "agent": row["agent"],
                "conversation_id": row["conversation_id"],
                "source": row["source"],
                "source_family": row["source_family"],
                "severity": row["severity"],
                "severity_rank": row["severity_rank"],
                "confidence": row["confidence"],
                "flags": json.loads(row["flags_json"] or "[]"),
                "primary_flag": row["primary_flag"],
                "reasons": json.loads(row["reasons_json"] or "[]"),
                "input_preview": row["input_preview"],
                "output_preview": row["output_preview"],
                "incident_marker": row["incident_marker"],
                "status": row["status"],
                "dedupe_fingerprint": row["dedupe_fingerprint"],
            }
        )
    return items


def security_alerts_summary(config_dir: Path | None = None) -> dict[str, Any]:
    with get_connection(config_dir) as conn:
        total_open = int(
            conn.execute(
                "SELECT COUNT(*) FROM security_alerts WHERE status = 'open'"
            ).fetchone()[0]
        )
        latest_row = conn.execute(
            "SELECT ts FROM security_alerts ORDER BY ts DESC LIMIT 1"
        ).fetchone()
    latest_at = float(latest_row["ts"]) if latest_row and latest_row["ts"] is not None else None
    return {
        "open_alert_count": total_open,
        "latest_alert_at": latest_at,
    }


def insert_runtime_event(config_dir: Path | None = None, record: dict[str, Any] | None = None) -> dict[str, Any]:
    payload = dict(record or {})
    payload.setdefault("id", str(uuid.uuid4()))
    payload.setdefault("ts", 0.0)
    payload.setdefault("agent_alias", "")
    payload.setdefault("agent_id", "")
    payload.setdefault("event_type", "")
    payload.setdefault("subtype", "")
    payload.setdefault("severity", "info")
    payload.setdefault("path", "")
    payload.setdefault("dst_host", "")
    payload.setdefault("dst_ip", "")
    payload.setdefault("dst_port", None)
    payload.setdefault("verdict", "")
    payload.setdefault("process_name", "")
    payload.setdefault("container_id", "")
    payload.setdefault("raw", {})
    with get_connection(config_dir) as conn:
        conn.execute(
            """
            INSERT INTO runtime_events (
                id, ts, agent_alias, agent_id, event_type, subtype, severity,
                path, dst_host, dst_ip, dst_port, verdict, process_name,
                container_id, raw_json
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                payload["id"],
                float(payload["ts"]),
                str(payload["agent_alias"]),
                str(payload["agent_id"]),
                str(payload["event_type"]),
                str(payload["subtype"]),
                str(payload["severity"]),
                str(payload["path"]),
                str(payload["dst_host"]),
                str(payload["dst_ip"]),
                int(payload["dst_port"]) if payload["dst_port"] is not None else None,
                str(payload["verdict"]),
                str(payload["process_name"]),
                str(payload["container_id"]),
                json.dumps(payload["raw"], ensure_ascii=True),
            ),
        )
        conn.commit()
    return payload


def list_runtime_events(
    config_dir: Path | None = None,
    *,
    limit: int = 100,
    agent: str = "",
    event_type: str = "",
) -> list[dict[str, Any]]:
    clauses: list[str] = []
    params: list[Any] = []
    if agent:
        clauses.append("agent_alias = ?")
        params.append(agent)
    if event_type:
        clauses.append("event_type = ?")
        params.append(event_type)
    where_sql = f"WHERE {' AND '.join(clauses)}" if clauses else ""
    query = f"""
        SELECT id, ts, agent_alias, agent_id, event_type, subtype, severity,
               path, dst_host, dst_ip, dst_port, verdict, process_name,
               container_id, raw_json
        FROM runtime_events
        {where_sql}
        ORDER BY ts DESC
        LIMIT ?
    """
    params.append(max(1, int(limit)))
    with get_connection(config_dir) as conn:
        rows = conn.execute(query, params).fetchall()
    return [
        {
            "id": row["id"],
            "ts": row["ts"],
            "agent_alias": row["agent_alias"],
            "agent_id": row["agent_id"],
            "event_type": row["event_type"],
            "subtype": row["subtype"],
            "severity": row["severity"],
            "path": row["path"],
            "dst_host": row["dst_host"],
            "dst_ip": row["dst_ip"],
            "dst_port": row["dst_port"],
            "verdict": row["verdict"],
            "process_name": row["process_name"],
            "container_id": row["container_id"],
            "raw": json.loads(row["raw_json"] or "{}"),
        }
        for row in rows
    ]
