"""SQLite-backed response cache for AgentGuard judge calls.

Cache key = SHA256(question + answer). TTL-based expiry.
Purpose:    Caches judge responses keyed by SHA256(question+answer) with configurable TTL and max-entry eviction. Reduces redundant Haiku/Sonnet API calls on repeated or similar queries.
Exports:    ResponseCache
Added:      v0.1.0
Status:     Stable
"""
from __future__ import annotations
import hashlib
import json
import sqlite3
import time
from pathlib import Path
from typing import Optional


class ResponseCache:
    """SQLite-backed TTL cache for judge responses.

    Args:
        db_path: SQLite file path. ':memory:' for in-memory (default).
        ttl_seconds: entries older than this expire. 0 = no expiry.
        max_entries: evict oldest when exceeded.
    """

    def __init__(
        self,
        db_path: str = ":memory:",
        ttl_seconds: int = 3600,
        max_entries: int = 10000,
    ) -> None:
        self.db_path = db_path
        self.ttl_seconds = ttl_seconds
        self.max_entries = max_entries
        self._hits = 0
        self._misses = 0
        if db_path != ":memory:":
            Path(db_path).parent.mkdir(parents=True, exist_ok=True)
        self._conn = sqlite3.connect(db_path, check_same_thread=False)
        self._init_db()

    def _init_db(self) -> None:
        self._conn.execute("""
            CREATE TABLE IF NOT EXISTS response_cache (
                cache_key  TEXT PRIMARY KEY,
                value_json TEXT NOT NULL,
                created_at REAL NOT NULL
            )
        """)
        self._conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_created_at ON response_cache(created_at)"
        )
        self._conn.commit()

    @staticmethod
    def make_key(question: str, answer: str) -> str:
        """Create a SHA256 cache key from question + answer."""
        content = f"{question.strip()}\x00{answer.strip()}"
        return hashlib.sha256(content.encode()).hexdigest()

    def get(self, key: str) -> Optional[dict]:
        """Return cached value or None if missing/expired."""
        row = self._conn.execute(
            "SELECT value_json, created_at FROM response_cache WHERE cache_key = ?",
            (key,),
        ).fetchone()
        if row is None:
            self._misses += 1
            return None
        value_json, created_at = row
        if self.ttl_seconds > 0 and (time.time() - created_at) > self.ttl_seconds:
            self._conn.execute(
                "DELETE FROM response_cache WHERE cache_key = ?", (key,)
            )
            self._conn.commit()
            self._misses += 1
            return None
        self._hits += 1
        return json.loads(value_json)

    def put(self, key: str, value: dict) -> None:
        """Store a value in the cache."""
        self._conn.execute(
            """
            INSERT INTO response_cache (cache_key, value_json, created_at)
            VALUES (?, ?, ?)
            ON CONFLICT(cache_key) DO UPDATE SET
                value_json = excluded.value_json,
                created_at = excluded.created_at
            """,
            (key, json.dumps(value), time.time()),
        )
        self._conn.commit()
        self._evict_if_needed()

    def _evict_if_needed(self) -> None:
        count = self._conn.execute(
            "SELECT COUNT(*) FROM response_cache"
        ).fetchone()[0]
        if count > self.max_entries:
            excess = count - self.max_entries
            self._conn.execute(
                """
                DELETE FROM response_cache WHERE cache_key IN (
                    SELECT cache_key FROM response_cache
                    ORDER BY created_at ASC LIMIT ?
                )
                """,
                (excess,),
            )
            self._conn.commit()

    def stats(self) -> dict:
        """Return hit/miss statistics."""
        count = self._conn.execute(
            "SELECT COUNT(*) FROM response_cache"
        ).fetchone()[0]
        total = self._hits + self._misses
        return {
            "hits": self._hits,
            "misses": self._misses,
            "hit_rate": self._hits / max(1, total),
            "size": count,
        }
