"""SQLite append-only AMCS event store."""

from __future__ import annotations

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

from amcs.canonical_json import canonical_dumps
from amcs.hashing import event_hash
from amcs.merkle import merkle_root_hex
from amcs.types import EventValidationError, validate_event_envelope


class SQLiteEventStore:
    """SQLite-backed append-only store for AMCS events."""

    def __init__(self, db_path: str = ":memory:") -> None:
        self.db_path = db_path
        self._db_file_created = False
        if db_path != ":memory:":
            db_file = Path(db_path)
            db_file.parent.mkdir(parents=True, exist_ok=True)
            self._db_file_created = not db_file.exists()
        self._conn = sqlite3.connect(db_path, check_same_thread=False)
        self._conn.row_factory = sqlite3.Row
        self._apply_security_pragmas()
        self._harden_db_file_permissions()
        self._init_schema()

    def _apply_security_pragmas(self) -> None:
        # Harden SQLite behavior when opening potentially untrusted local files.
        try:
            self._conn.execute("PRAGMA trusted_schema=OFF")
        except sqlite3.OperationalError:
            pass
        self._conn.execute("PRAGMA foreign_keys=ON")

    def _harden_db_file_permissions(self) -> None:
        if self.db_path == ":memory:" or not self._db_file_created:
            return
        try:
            Path(self.db_path).chmod(0o600)
        except OSError:
            # Best effort only; do not fail initialization on
            # platform-specific permission issues.
            return

    def _init_schema(self) -> None:
        with self._conn:
            self._conn.execute(
                """
                CREATE TABLE IF NOT EXISTS events (
                    agent_id TEXT NOT NULL,
                    sequence INTEGER NOT NULL,
                    event_json TEXT NOT NULL,
                    event_hash TEXT NOT NULL,
                    timestamp TEXT NOT NULL,
                    PRIMARY KEY(agent_id, sequence)
                )
                """
            )
            self._conn.execute(
                """
                CREATE TABLE IF NOT EXISTS roots (
                    agent_id TEXT NOT NULL,
                    sequence INTEGER NOT NULL,
                    memory_root TEXT NOT NULL,
                    PRIMARY KEY(agent_id, sequence)
                )
                """
            )

    def close(self) -> None:
        self._conn.close()

    def append_event(self, agent_id: str, event_envelope: dict[str, Any]) -> dict[str, Any]:
        event = dict(event_envelope)
        event.setdefault("sequence", None)

        if event.get("agent_id") != agent_id:
            raise EventValidationError("event agent_id must match append agent_id")

        with self._conn:
            row = self._conn.execute(
                "SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM events WHERE agent_id = ?",
                (agent_id,),
            ).fetchone()
            sequence = int(row["next_seq"])
            event["sequence"] = sequence
            validate_event_envelope(event)

            event_json = canonical_dumps(event)
            digest = event_hash(event)
            timestamp = str(event["timestamp"])

            self._conn.execute(
                """
                INSERT INTO events(agent_id, sequence, event_json, event_hash, timestamp)
                VALUES (?, ?, ?, ?, ?)
                """,
                (agent_id, sequence, event_json, digest, timestamp),
            )

            memory_root = self._compute_memory_root(agent_id)
            self._conn.execute(
                """
                INSERT INTO roots(agent_id, sequence, memory_root)
                VALUES (?, ?, ?)
                """,
                (agent_id, sequence, memory_root),
            )

        return {
            "agent_id": agent_id,
            "sequence": sequence,
            "event_hash": digest,
            "memory_root": memory_root,
        }

    def get_events(
        self,
        agent_id: str,
        from_seq: int = 1,
        to_seq: int | None = None,
    ) -> list[dict[str, Any]]:
        query = """
            SELECT agent_id, sequence, event_json, event_hash, timestamp
            FROM events
            WHERE agent_id = ? AND sequence >= ?
        """
        params: list[Any] = [agent_id, from_seq]
        if to_seq is not None:
            query += " AND sequence <= ?"
            params.append(to_seq)
        query += " ORDER BY sequence ASC"

        rows = self._conn.execute(query, params).fetchall()
        return [
            {
                "agent_id": row["agent_id"],
                "sequence": int(row["sequence"]),
                "event": json.loads(row["event_json"]),
                "event_json": row["event_json"],
                "event_hash": row["event_hash"],
                "timestamp": row["timestamp"],
            }
            for row in rows
        ]

    def get_event_hashes(
        self,
        agent_id: str,
        from_seq: int,
        to_seq: int,
    ) -> list[str]:
        rows = self._conn.execute(
            """
            SELECT event_hash
            FROM events
            WHERE agent_id = ? AND sequence >= ? AND sequence <= ?
            ORDER BY sequence ASC
            """,
            (agent_id, from_seq, to_seq),
        ).fetchall()
        return [str(row["event_hash"]) for row in rows]

    def get_latest_sequence(self, agent_id: str) -> int | None:
        row = self._conn.execute(
            """
            SELECT MAX(sequence) AS latest
            FROM events
            WHERE agent_id = ?
            """,
            (agent_id,),
        ).fetchone()
        latest = row["latest"]
        return None if latest is None else int(latest)

    def get_memory_root(self, agent_id: str, seq: int | None = None) -> str | None:
        if seq is None:
            row = self._conn.execute(
                """
                SELECT memory_root
                FROM roots
                WHERE agent_id = ?
                ORDER BY sequence DESC
                LIMIT 1
                """,
                (agent_id,),
            ).fetchone()
        else:
            row = self._conn.execute(
                """
                SELECT memory_root
                FROM roots
                WHERE agent_id = ? AND sequence = ?
                """,
                (agent_id, seq),
            ).fetchone()

        return None if row is None else str(row["memory_root"])

    def _compute_memory_root(self, agent_id: str) -> str:
        latest = self.get_latest_sequence(agent_id)
        if latest is None:
            return merkle_root_hex([])
        leaf_hashes = self.get_event_hashes(agent_id, from_seq=1, to_seq=latest)
        return merkle_root_hex(leaf_hashes)
