"""Bounded, privacy-preserving journal of operator security activity."""

import secrets
import sqlite3
from dataclasses import dataclass
from pathlib import Path
from typing import Callable

from src.private_state import connect_private_sqlite


class SecurityEventStoreError(RuntimeError):
    """Raised when security activity cannot be persisted or read safely."""


@dataclass(frozen=True)
class SecurityEvent:
    id: int
    kind: str
    method: str | None
    device_label: str | None
    target: str | None
    created_at: int
    status: str


@dataclass(frozen=True)
class SecurityEventPage:
    events: list[SecurityEvent]
    next_cursor: int | None


class SecurityEventStore:
    def __init__(
        self,
        path: str | Path,
        *,
        clock: Callable[[], float],
        max_events: int = 10_000,
        retention_seconds: int = 90 * 24 * 60 * 60,
        lock_timeout_seconds: float = 0.1,
    ) -> None:
        self.path = Path(path)
        self.clock = clock
        self.max_events = max(1, max_events)
        self.retention_seconds = max(1, retention_seconds)
        self.lock_timeout_seconds = lock_timeout_seconds

    @staticmethod
    def _bounded(value: str | None, limit: int) -> str | None:
        if value is None:
            return None
        normalized = " ".join(str(value).split())[:limit]
        return normalized or None

    def _connect(self) -> sqlite3.Connection:
        try:
            connection = connect_private_sqlite(self.path, timeout=self.lock_timeout_seconds)
            connection.execute(
                """
                CREATE TABLE IF NOT EXISTS security_events (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    kind TEXT NOT NULL,
                    method TEXT,
                    device_label TEXT,
                    target TEXT,
                    created_at INTEGER NOT NULL,
                    status TEXT NOT NULL DEFAULT 'completed',
                    operation_id TEXT
                )
                """
            )
            columns = {
                row[1] for row in connection.execute("PRAGMA table_info(security_events)")
            }
            if "status" not in columns:
                connection.execute(
                    "ALTER TABLE security_events ADD COLUMN status TEXT NOT NULL DEFAULT 'completed'"
                )
            if "operation_id" not in columns:
                connection.execute(
                    "ALTER TABLE security_events ADD COLUMN operation_id TEXT"
                )
            connection.execute(
                "CREATE INDEX IF NOT EXISTS security_events_created "
                "ON security_events(created_at DESC, id DESC)"
            )
            connection.execute(
                "CREATE UNIQUE INDEX IF NOT EXISTS security_events_operation "
                "ON security_events(operation_id) WHERE operation_id IS NOT NULL"
            )
            return connection
        except (OSError, sqlite3.Error) as exc:
            raise SecurityEventStoreError(
                "Security activity is temporarily unavailable"
            ) from exc

    def _prune(self, connection: sqlite3.Connection, now: int) -> None:
        connection.execute(
            "DELETE FROM security_events WHERE created_at < ?",
            (now - self.retention_seconds,),
        )
        connection.execute(
            "DELETE FROM security_events WHERE id NOT IN "
            "(SELECT id FROM security_events ORDER BY id DESC LIMIT ?)",
            (self.max_events,),
        )

    def record(
        self,
        kind: str,
        *,
        method: str | None = None,
        device_label: str | None = None,
        target: str | None = None,
    ) -> None:
        now = int(self.clock())
        try:
            with self._connect() as connection:
                connection.execute(
                    "INSERT INTO security_events(kind, method, device_label, target, created_at, status) "
                    "VALUES (?, ?, ?, ?, ?, 'completed')",
                    (
                        self._bounded(kind, 48) or "security_event",
                        self._bounded(method, 32),
                        self._bounded(device_label, 64),
                        self._bounded(target, 255),
                        now,
                    ),
                )
                self._prune(connection, now)
        except (OSError, sqlite3.Error) as exc:
            raise SecurityEventStoreError(
                "Security activity is temporarily unavailable"
            ) from exc

    def reserve(
        self,
        kind: str,
        *,
        method: str | None = None,
        device_label: str | None = None,
        target: str | None = None,
    ) -> str:
        now = int(self.clock())
        operation_id = secrets.token_urlsafe(24)
        try:
            with self._connect() as connection:
                connection.execute(
                    "INSERT INTO security_events(kind, method, device_label, target, created_at, status, operation_id) "
                    "VALUES (?, ?, ?, ?, ?, 'pending', ?)",
                    (
                        self._bounded(kind, 48) or "security_event",
                        self._bounded(method, 32),
                        self._bounded(device_label, 64),
                        self._bounded(target, 255),
                        now,
                        operation_id,
                    ),
                )
                self._prune(connection, now)
        except (OSError, sqlite3.Error) as exc:
            raise SecurityEventStoreError(
                "Security activity is temporarily unavailable"
            ) from exc
        return operation_id

    def finalize(self, operation_id: str) -> None:
        try:
            with self._connect() as connection:
                cursor = connection.execute(
                    "UPDATE security_events SET status = 'completed' WHERE operation_id = ?",
                    (operation_id,),
                )
                if cursor.rowcount != 1:
                    raise SecurityEventStoreError(
                        "Security activity reservation was not found"
                    )
        except (OSError, sqlite3.Error) as exc:
            raise SecurityEventStoreError(
                "Security activity is temporarily unavailable"
            ) from exc

    def discard(self, operation_id: str) -> None:
        try:
            with self._connect() as connection:
                connection.execute(
                    "DELETE FROM security_events WHERE operation_id = ? AND status = 'pending'",
                    (operation_id,),
                )
        except (OSError, sqlite3.Error) as exc:
            raise SecurityEventStoreError(
                "Security activity is temporarily unavailable"
            ) from exc

    def list(self, *, limit: int = 50, cursor: int | None = None) -> SecurityEventPage:
        bounded_limit = min(100, max(1, limit))
        parameters: list[int] = []
        where = ""
        if cursor is not None:
            where = "WHERE id < ?"
            parameters.append(cursor)
        parameters.append(bounded_limit + 1)
        try:
            with self._connect() as connection:
                rows = connection.execute(
                    "SELECT id, kind, method, device_label, target, created_at, status "
                    f"FROM security_events {where} ORDER BY id DESC LIMIT ?",
                    parameters,
                ).fetchall()
        except (OSError, sqlite3.Error) as exc:
            raise SecurityEventStoreError(
                "Security activity is temporarily unavailable"
            ) from exc
        has_more = len(rows) > bounded_limit
        visible = rows[:bounded_limit]
        return SecurityEventPage(
            events=[SecurityEvent(*row) for row in visible],
            next_cursor=visible[-1][0] if has_more else None,
        )
