"""Semantic response cache — avoid re-sending identical requests."""
from __future__ import annotations

import hashlib
import json
from collections import OrderedDict
from dataclasses import dataclass


@dataclass
class _CacheEntry:
    response: str
    input_tokens: int
    output_tokens: int


class SemanticCache:
    """LRU cache for LLM responses keyed by content + model + tools hash.

    Cache key is an MD5 hash of (prompt_content, model, tool_schemas_json).
    This catches exact-match repeated queries within a session.
    """

    def __init__(self, *, max_entries: int = 256) -> None:
        self._max_entries = max_entries
        self._cache: OrderedDict[str, _CacheEntry] = OrderedDict()
        self._hits = 0
        self._misses = 0
        self._tokens_saved = 0

    def _make_key(
        self,
        prompt: str,
        model: str,
        tool_schemas: list[dict] | None = None,
    ) -> str:
        parts = [prompt, model]
        if tool_schemas:
            parts.append(json.dumps(tool_schemas, sort_keys=True, separators=(",", ":")))
        raw = "\n---\n".join(parts)
        return hashlib.md5(raw.encode()).hexdigest()

    def get(
        self,
        prompt: str,
        model: str,
        *,
        tool_schemas: list[dict] | None = None,
    ) -> dict | None:
        """Look up a cached response. Returns dict with response, input_tokens, output_tokens or None."""
        key = self._make_key(prompt, model, tool_schemas)
        entry = self._cache.get(key)
        if entry is None:
            self._misses += 1
            return None
        self._cache.move_to_end(key)
        self._hits += 1
        self._tokens_saved += entry.input_tokens
        return {
            "response": entry.response,
            "input_tokens": entry.input_tokens,
            "output_tokens": entry.output_tokens,
        }

    def put(
        self,
        prompt: str,
        model: str,
        *,
        response: str,
        input_tokens: int,
        output_tokens: int,
        tool_schemas: list[dict] | None = None,
    ) -> None:
        """Store a response in the cache."""
        key = self._make_key(prompt, model, tool_schemas)
        self._cache[key] = _CacheEntry(
            response=response,
            input_tokens=input_tokens,
            output_tokens=output_tokens,
        )
        self._cache.move_to_end(key)
        while len(self._cache) > self._max_entries:
            self._cache.popitem(last=False)

    def clear(self) -> None:
        """Clear the cache and reset stats."""
        self._cache.clear()
        self._hits = 0
        self._misses = 0
        self._tokens_saved = 0

    def stats(self) -> dict:
        """Return cache statistics."""
        return {
            "hits": self._hits,
            "misses": self._misses,
            "tokens_saved": self._tokens_saved,
            "size": len(self._cache),
        }
