"""SQLite-backed persistence for DAG snapshots, KV store, audit log.

Uses stdlib sqlite3 only (Tier 0 compliant). Provides durable storage
for DAG versioning, arbitrary key-value pairs, and audit events.
"""

from __future__ import annotations

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


def _now() -> str:
    """ISO-format UTC timestamp."""
    return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())


_SCHEMA_SQL = """\
CREATE TABLE IF NOT EXISTS dag_snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    dag_json TEXT NOT NULL,
    metadata TEXT,
    created_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS kv_store (
    key TEXT PRIMARY KEY,
    value TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS audit_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    event_type TEXT NOT NULL,
    actor TEXT NOT NULL,
    details TEXT NOT NULL DEFAULT '',
    created_at TEXT NOT NULL
);
"""


class SQLiteStore:
    """SQLite-backed persistence for DAG snapshots, KV, audit.

    Usage::

        with SQLiteStore("orxaq.db") as store:
            sid = store.save_dag_snapshot("v1", dag_json)
            store.put("key", "value")

    Or without context manager::

        store = SQLiteStore(":memory:")
        store.put("k", "v")
        store.close()
    """

    def __init__(self, db_path: str | Path = ":memory:") -> None:
        """Open or create SQLite DB."""
        self._path = str(db_path)
        self._conn = sqlite3.connect(self._path)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.executescript(_SCHEMA_SQL)

    # ── Context manager ────────────────────────────────────

    def __enter__(self) -> SQLiteStore:
        return self

    def __exit__(self, *_: Any) -> None:
        self.close()

    # ── DAG snapshots ──────────────────────────────────────

    def save_dag_snapshot(
        self,
        name: str,
        dag_json: str,
        metadata: dict | None = None,
    ) -> int:
        """Save a DAG snapshot. Returns snapshot ID."""
        meta_str = json.dumps(metadata) if metadata else None
        cur = self._conn.execute(
            "INSERT INTO dag_snapshots (name, dag_json, metadata, created_at) VALUES (?, ?, ?, ?)",
            (name, dag_json, meta_str, _now()),
        )
        self._conn.commit()
        return cur.lastrowid  # type: ignore[return-value]

    def load_dag_snapshot(self, snapshot_id: int) -> dict | None:
        """Load a snapshot by ID."""
        row = self._conn.execute(
            "SELECT id, name, dag_json, metadata, created_at FROM dag_snapshots WHERE id = ?",
            (snapshot_id,),
        ).fetchone()
        if row is None:
            return None
        meta = json.loads(row["metadata"]) if row["metadata"] else None
        return {
            "id": row["id"],
            "name": row["name"],
            "dag_json": row["dag_json"],
            "metadata": meta,
            "created_at": row["created_at"],
        }

    def list_dag_snapshots(self, limit: int = 50) -> list[dict]:
        """List recent snapshots (newest first)."""
        rows = self._conn.execute(
            "SELECT id, name, created_at FROM dag_snapshots ORDER BY id DESC LIMIT ?",
            (limit,),
        ).fetchall()
        return [
            {
                "id": r["id"],
                "name": r["name"],
                "created_at": r["created_at"],
            }
            for r in rows
        ]

    def delete_dag_snapshot(self, snapshot_id: int) -> bool:
        """Delete a snapshot. Returns True if deleted."""
        cur = self._conn.execute(
            "DELETE FROM dag_snapshots WHERE id = ?",
            (snapshot_id,),
        )
        self._conn.commit()
        return cur.rowcount > 0

    # ── Key-value store ────────────────────────────────────

    def put(self, key: str, value: str) -> None:
        """Upsert a key-value pair."""
        self._conn.execute(
            "INSERT INTO kv_store "
            "(key, value, updated_at) "
            "VALUES (?, ?, ?) "
            "ON CONFLICT(key) DO UPDATE SET "
            "value=excluded.value, "
            "updated_at=excluded.updated_at",
            (key, value, _now()),
        )
        self._conn.commit()

    def get(self, key: str) -> str | None:
        """Get value by key."""
        row = self._conn.execute(
            "SELECT value FROM kv_store WHERE key = ?",
            (key,),
        ).fetchone()
        return row["value"] if row else None

    def delete(self, key: str) -> bool:
        """Delete a key. Returns True if existed."""
        cur = self._conn.execute(
            "DELETE FROM kv_store WHERE key = ?",
            (key,),
        )
        self._conn.commit()
        return cur.rowcount > 0

    def keys(self, prefix: str = "") -> list[str]:
        """List keys matching prefix."""
        if prefix:
            rows = self._conn.execute(
                "SELECT key FROM kv_store WHERE key LIKE ? ORDER BY key",
                (prefix + "%",),
            ).fetchall()
        else:
            rows = self._conn.execute("SELECT key FROM kv_store ORDER BY key").fetchall()
        return [r["key"] for r in rows]

    # ── Audit log ──────────────────────────────────────────

    def log_event(
        self,
        event_type: str,
        actor: str,
        details: str = "",
    ) -> int:
        """Log an audit event. Returns event ID."""
        cur = self._conn.execute(
            "INSERT INTO audit_log (event_type, actor, details, created_at) VALUES (?, ?, ?, ?)",
            (event_type, actor, details, _now()),
        )
        self._conn.commit()
        return cur.lastrowid  # type: ignore[return-value]

    def query_events(
        self,
        event_type: str | None = None,
        limit: int = 100,
    ) -> list[dict]:
        """Query audit events, optionally by type."""
        if event_type:
            rows = self._conn.execute(
                "SELECT id, event_type, actor, "
                "details, created_at "
                "FROM audit_log "
                "WHERE event_type = ? "
                "ORDER BY id DESC LIMIT ?",
                (event_type, limit),
            ).fetchall()
        else:
            rows = self._conn.execute(
                "SELECT id, event_type, actor, "
                "details, created_at "
                "FROM audit_log "
                "ORDER BY id DESC LIMIT ?",
                (limit,),
            ).fetchall()
        return [
            {
                "id": r["id"],
                "event_type": r["event_type"],
                "actor": r["actor"],
                "details": r["details"],
                "created_at": r["created_at"],
            }
            for r in rows
        ]

    # ── Lifecycle ──────────────────────────────────────────

    def close(self) -> None:
        """Close the database connection."""
        self._conn.close()


__all__ = ["SQLiteStore"]
