"""Tests for SQLiteStore persistence layer."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from orxaq.fabric.persistence import SQLiteStore

# ── Initialization ─────────────────────────────────────────


class TestSQLiteStoreInit:
    """Verify table creation and connection modes."""

    def test_in_memory_creates_tables(self) -> None:
        store = SQLiteStore(":memory:")
        rows = store.list_dag_snapshots()
        assert rows == []
        store.close()

    def test_file_based_creates_db(self, tmp_path: Path) -> None:
        db_file = tmp_path / "test.db"
        store = SQLiteStore(db_file)
        assert db_file.exists()
        store.close()

    def test_reopen_preserves_data(self, tmp_path: Path) -> None:
        db_file = tmp_path / "reopen.db"
        s1 = SQLiteStore(db_file)
        s1.put("k1", "v1")
        s1.close()

        s2 = SQLiteStore(db_file)
        assert s2.get("k1") == "v1"
        s2.close()

    def test_context_manager(self) -> None:
        with SQLiteStore() as store:
            store.put("ctx", "ok")
            assert store.get("ctx") == "ok"


# ── DAG Snapshots ─────────────────────────────────────────


class TestDAGSnapshots:
    """CRUD operations for DAG snapshots."""

    @pytest.fixture()
    def store(self) -> SQLiteStore:
        s = SQLiteStore(":memory:")
        yield s
        s.close()

    def test_save_returns_id(self, store: SQLiteStore) -> None:
        sid = store.save_dag_snapshot("v1", '{"nodes":[]}')
        assert isinstance(sid, int)
        assert sid >= 1

    def test_load_existing(self, store: SQLiteStore) -> None:
        dag = json.dumps({"nodes": ["a", "b"]})
        sid = store.save_dag_snapshot("snap1", dag)
        loaded = store.load_dag_snapshot(sid)
        assert loaded is not None
        assert loaded["name"] == "snap1"
        assert loaded["dag_json"] == dag
        assert loaded["created_at"] is not None

    def test_load_nonexistent(self, store: SQLiteStore) -> None:
        assert store.load_dag_snapshot(9999) is None

    def test_save_with_metadata(self, store: SQLiteStore) -> None:
        meta = {"author": "test", "version": 3}
        sid = store.save_dag_snapshot("meta-snap", "{}", metadata=meta)
        loaded = store.load_dag_snapshot(sid)
        assert loaded is not None
        assert loaded["metadata"] == meta

    def test_save_without_metadata(self, store: SQLiteStore) -> None:
        sid = store.save_dag_snapshot("bare", "{}")
        loaded = store.load_dag_snapshot(sid)
        assert loaded is not None
        assert loaded["metadata"] is None

    def test_list_snapshots_ordered(self, store: SQLiteStore) -> None:
        store.save_dag_snapshot("first", "{}")
        store.save_dag_snapshot("second", "{}")
        store.save_dag_snapshot("third", "{}")
        snaps = store.list_dag_snapshots()
        assert len(snaps) == 3
        assert snaps[0]["name"] == "third"
        assert snaps[2]["name"] == "first"

    def test_list_snapshots_limit(self, store: SQLiteStore) -> None:
        for i in range(10):
            store.save_dag_snapshot(f"s{i}", "{}")
        snaps = store.list_dag_snapshots(limit=3)
        assert len(snaps) == 3

    def test_delete_existing(self, store: SQLiteStore) -> None:
        sid = store.save_dag_snapshot("del", "{}")
        assert store.delete_dag_snapshot(sid) is True
        assert store.load_dag_snapshot(sid) is None

    def test_delete_nonexistent(self, store: SQLiteStore) -> None:
        assert store.delete_dag_snapshot(9999) is False


# ── Key-Value Store ────────────────────────────────────────


class TestKVStore:
    """Key-value put/get/delete/keys operations."""

    @pytest.fixture()
    def store(self) -> SQLiteStore:
        s = SQLiteStore(":memory:")
        yield s
        s.close()

    def test_put_and_get(self, store: SQLiteStore) -> None:
        store.put("hello", "world")
        assert store.get("hello") == "world"

    def test_get_missing(self, store: SQLiteStore) -> None:
        assert store.get("nonexistent") is None

    def test_upsert_overwrites(self, store: SQLiteStore) -> None:
        store.put("key", "v1")
        store.put("key", "v2")
        assert store.get("key") == "v2"

    def test_delete_existing(self, store: SQLiteStore) -> None:
        store.put("rm", "val")
        assert store.delete("rm") is True
        assert store.get("rm") is None

    def test_delete_missing(self, store: SQLiteStore) -> None:
        assert store.delete("nope") is False

    def test_keys_all(self, store: SQLiteStore) -> None:
        store.put("a", "1")
        store.put("b", "2")
        store.put("c", "3")
        assert store.keys() == ["a", "b", "c"]

    def test_keys_prefix(self, store: SQLiteStore) -> None:
        store.put("dag:v1", "x")
        store.put("dag:v2", "y")
        store.put("meta:v1", "z")
        assert store.keys("dag:") == [
            "dag:v1",
            "dag:v2",
        ]

    def test_keys_empty(self, store: SQLiteStore) -> None:
        assert store.keys() == []


# ── Audit Log ─────────────────────────────────────────────


class TestAuditLog:
    """Audit event logging and querying."""

    @pytest.fixture()
    def store(self) -> SQLiteStore:
        s = SQLiteStore(":memory:")
        yield s
        s.close()

    def test_log_event_returns_id(self, store: SQLiteStore) -> None:
        eid = store.log_event("login", "alice")
        assert isinstance(eid, int)
        assert eid >= 1

    def test_log_event_with_details(self, store: SQLiteStore) -> None:
        eid = store.log_event("error", "system", "disk full")
        events = store.query_events()
        assert len(events) == 1
        assert events[0]["details"] == "disk full"
        assert events[0]["id"] == eid

    def test_query_all(self, store: SQLiteStore) -> None:
        store.log_event("a", "u1")
        store.log_event("b", "u2")
        store.log_event("a", "u3")
        events = store.query_events()
        assert len(events) == 3

    def test_query_by_type(self, store: SQLiteStore) -> None:
        store.log_event("login", "alice")
        store.log_event("logout", "bob")
        store.log_event("login", "carol")
        events = store.query_events(event_type="login")
        assert len(events) == 2
        assert all(e["event_type"] == "login" for e in events)

    def test_query_limit(self, store: SQLiteStore) -> None:
        for i in range(20):
            store.log_event("tick", f"actor{i}")
        events = store.query_events(limit=5)
        assert len(events) == 5

    def test_query_empty(self, store: SQLiteStore) -> None:
        assert store.query_events() == []

    def test_event_fields(self, store: SQLiteStore) -> None:
        store.log_event("deploy", "ci", "sha=abc123")
        ev = store.query_events()[0]
        assert ev["event_type"] == "deploy"
        assert ev["actor"] == "ci"
        assert ev["details"] == "sha=abc123"
        assert "created_at" in ev
        assert "id" in ev
