"""SQLite-backed disk buffer for crash-safe telemetry delivery.

Events survive process crashes and restarts. The buffer is thread-safe
and designed for a single-writer (main thread puts) / single-reader
(background worker drains) pattern, though concurrent access is safe.
"""

from __future__ import annotations

import json
import os
import sqlite3
import threading
import uuid
from typing import Any, Optional


# Default max buffer size (events). Oldest events dropped if exceeded.
DEFAULT_MAX_SIZE = 10_000

# Default database path
DEFAULT_DB_DIR = os.path.join(os.path.expanduser("~"), ".genosis")
DEFAULT_DB_PATH = os.path.join(DEFAULT_DB_DIR, "buffer.db")


class DiskBuffer:
    """Thread-safe, crash-safe event buffer backed by SQLite.

    Events are written to a local SQLite database. They persist across
    process restarts. The background worker drains events in batches
    and removes them only after server acknowledgment.

    Usage:
        buf = DiskBuffer()
        buf.put("telemetry", {"event_id": "abc", ...})
        batch = buf.peek(50)
        # ... send batch to server, get ack ...
        buf.remove([e["id"] for e in batch])
    """

    def __init__(
        self,
        path: Optional[str] = None,
        max_size: int = DEFAULT_MAX_SIZE,
    ) -> None:
        self._path = path or DEFAULT_DB_PATH
        self._max_size = max_size
        self._lock = threading.Lock()

        # Ensure directory exists
        db_dir = os.path.dirname(self._path)
        if db_dir:
            os.makedirs(db_dir, exist_ok=True)

        self._conn = sqlite3.connect(self._path, check_same_thread=False)
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.execute("PRAGMA synchronous=NORMAL")
        self._conn.execute("""
            CREATE TABLE IF NOT EXISTS pending (
                id TEXT PRIMARY KEY,
                type TEXT NOT NULL,
                payload TEXT NOT NULL,
                created_at TEXT NOT NULL DEFAULT (datetime('now'))
            )
        """)
        self._conn.commit()

    def put(self, event_type: str, payload: dict[str, Any]) -> str:
        """Add an event to the buffer. Returns the event ID.

        This is called from the main thread during g.call() and must be fast.
        """
        event_id = payload.get("event_id") or str(uuid.uuid4())
        with self._lock:
            self._conn.execute(
                "INSERT OR IGNORE INTO pending (id, type, payload) VALUES (?, ?, ?)",
                (event_id, event_type, json.dumps(payload)),
            )
            self._conn.commit()
            self._enforce_max_size()
        return event_id

    def peek(self, limit: int = 50) -> list[dict[str, Any]]:
        """Read up to `limit` events without removing them.

        Returns list of dicts with: id, type, payload (parsed JSON).
        """
        with self._lock:
            cursor = self._conn.execute(
                "SELECT id, type, payload FROM pending ORDER BY created_at ASC LIMIT ?",
                (limit,),
            )
            rows = cursor.fetchall()

        result = []
        for row_id, row_type, row_payload in rows:
            try:
                parsed = json.loads(row_payload)
            except (json.JSONDecodeError, TypeError):
                parsed = {}
            result.append({"id": row_id, "type": row_type, "payload": parsed})
        return result

    def remove(self, event_ids: list[str]) -> int:
        """Remove confirmed events from the buffer. Returns count removed."""
        if not event_ids:
            return 0
        with self._lock:
            placeholders = ",".join("?" for _ in event_ids)
            cursor = self._conn.execute(
                f"DELETE FROM pending WHERE id IN ({placeholders})",
                event_ids,
            )
            self._conn.commit()
            return cursor.rowcount

    def size(self) -> int:
        """Number of events currently in the buffer."""
        with self._lock:
            cursor = self._conn.execute("SELECT COUNT(*) FROM pending")
            return cursor.fetchone()[0]

    def clear(self) -> None:
        """Remove all events from the buffer."""
        with self._lock:
            self._conn.execute("DELETE FROM pending")
            self._conn.commit()

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

    def _enforce_max_size(self) -> None:
        """Drop oldest events if buffer exceeds max_size. Called under lock."""
        cursor = self._conn.execute("SELECT COUNT(*) FROM pending")
        count = cursor.fetchone()[0]
        if count > self._max_size:
            excess = count - self._max_size
            self._conn.execute(
                "DELETE FROM pending WHERE id IN "
                "(SELECT id FROM pending ORDER BY created_at ASC LIMIT ?)",
                (excess,),
            )
            self._conn.commit()
