"""SQLite cache backend for pretok."""

from __future__ import annotations

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

from pretok.pipeline.cache import Cache

logger = logging.getLogger(__name__)


class SQLiteCache(Cache):
    """SQLite-based persistent cache backend.

    Provides a simple file-based cache that persists across process restarts.

    Example:
        >>> cache = SQLiteCache(path="~/.pretok/cache.db")
        >>> cache.set("key", "value")
        >>> cache.get("key")
        'value'
    """

    def __init__(
        self,
        path: str = "~/.pretok/cache.db",
        ttl: int = 604800,
    ) -> None:
        """Initialize SQLite cache.

        Args:
            path: Path to SQLite database file
            ttl: Time-to-live in seconds (0 = no expiry)
        """
        self._path = Path(path).expanduser()
        self._ttl = ttl
        self._hits = 0
        self._misses = 0

        # Ensure parent directory exists
        self._path.parent.mkdir(parents=True, exist_ok=True)

        # Initialize database
        self._conn = sqlite3.connect(str(self._path), check_same_thread=False)
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.execute(
            """
            CREATE TABLE IF NOT EXISTS cache (
                key TEXT PRIMARY KEY,
                value TEXT NOT NULL,
                created_at REAL NOT NULL
            )
            """
        )
        self._conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_cache_created_at ON cache(created_at)"
        )
        self._conn.commit()

        logger.info("Initialized SQLiteCache: path=%s, ttl=%d", self._path, ttl)

    def get(self, key: str) -> Any | None:
        """Get value from SQLite cache.

        Args:
            key: Cache key

        Returns:
            Cached value or None if not found/expired
        """
        cursor = self._conn.execute(
            "SELECT value, created_at FROM cache WHERE key = ?", (key,)
        )
        row = cursor.fetchone()

        if row is None:
            self._misses += 1
            return None

        value, created_at = row

        # Check TTL
        if self._ttl > 0 and (time.time() - created_at) > self._ttl:
            self._conn.execute("DELETE FROM cache WHERE key = ?", (key,))
            self._conn.commit()
            self._misses += 1
            return None

        self._hits += 1
        try:
            return json.loads(value)
        except (json.JSONDecodeError, TypeError):
            return value

    def set(self, key: str, value: Any) -> None:
        """Set value in SQLite cache.

        Args:
            key: Cache key
            value: Value to cache
        """
        serialized = json.dumps(value) if not isinstance(value, str) else value
        self._conn.execute(
            "INSERT OR REPLACE INTO cache (key, value, created_at) VALUES (?, ?, ?)",
            (key, serialized, time.time()),
        )
        self._conn.commit()

    def delete(self, key: str) -> bool:
        """Delete value from SQLite cache.

        Args:
            key: Cache key

        Returns:
            True if key was deleted
        """
        cursor = self._conn.execute("DELETE FROM cache WHERE key = ?", (key,))
        self._conn.commit()
        return cursor.rowcount > 0

    def clear(self) -> None:
        """Clear all entries from SQLite cache."""
        self._conn.execute("DELETE FROM cache")
        self._conn.commit()
        self._hits = 0
        self._misses = 0

    def size(self) -> int:
        """Return number of entries in cache."""
        cursor = self._conn.execute("SELECT COUNT(*) FROM cache")
        row = cursor.fetchone()
        return row[0] if row else 0

    def cleanup_expired(self) -> int:
        """Remove expired entries from cache.

        Returns:
            Number of entries removed
        """
        if self._ttl == 0:
            return 0

        cutoff = time.time() - self._ttl
        cursor = self._conn.execute(
            "DELETE FROM cache WHERE created_at < ?", (cutoff,)
        )
        self._conn.commit()
        return cursor.rowcount

    @property
    def hit_rate(self) -> float:
        """Return cache hit rate."""
        total = self._hits + self._misses
        if total == 0:
            return 0.0
        return self._hits / total

    @property
    def stats(self) -> dict[str, Any]:
        """Return cache statistics."""
        return {
            "backend": "sqlite",
            "path": str(self._path),
            "size": self.size(),
            "hits": self._hits,
            "misses": self._misses,
            "hit_rate": self.hit_rate,
        }

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

    def __del__(self) -> None:
        """Ensure connection is closed."""
        with contextlib.suppress(Exception):
            self._conn.close()
