"""
In-memory LRU cache for extraction results.

Cache key: hash(task_id + text + model_id)
TTL: configurable via EXTRACTION_CACHE_TTL env var (default 86400s = 24h)
"""

from __future__ import annotations

import hashlib
import os
import time
from collections import OrderedDict
from dataclasses import dataclass, field

import structlog

from .models import ExtractResponse

logger = structlog.get_logger(__name__)

CACHE_TTL = int(os.environ.get("EXTRACTION_CACHE_TTL", "86400"))
CACHE_MAX_SIZE = int(os.environ.get("EXTRACTION_CACHE_MAX_SIZE", "1000"))


@dataclass
class CacheEntry:
    response: ExtractResponse
    created_at: float = field(default_factory=time.monotonic)


class ExtractionCache:
    """Thread-safe LRU cache with TTL expiry."""

    def __init__(self, max_size: int = CACHE_MAX_SIZE, ttl: int = CACHE_TTL):
        self._store: OrderedDict[str, CacheEntry] = OrderedDict()
        self._max_size = max_size
        self._ttl = ttl
        self._hits = 0
        self._misses = 0

    @staticmethod
    def _make_key(text: str, task_id: str | None, model_id: str | None) -> str:
        raw = f"{task_id or ''}:{model_id or ''}:{text}"
        return hashlib.sha256(raw.encode()).hexdigest()

    def get(self, text: str, task_id: str | None, model_id: str | None) -> ExtractResponse | None:
        key = self._make_key(text, task_id, model_id)
        entry = self._store.get(key)

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

        # Check TTL
        age = time.monotonic() - entry.created_at
        if age > self._ttl:
            del self._store[key]
            self._misses += 1
            logger.debug("cache_expired", key=key[:12], age_s=round(age))
            return None

        # Move to end (most recently used)
        self._store.move_to_end(key)
        self._hits += 1
        logger.debug("cache_hit", key=key[:12])
        return entry.response

    def put(self, text: str, task_id: str | None, model_id: str | None, response: ExtractResponse) -> None:
        key = self._make_key(text, task_id, model_id)

        # Evict oldest if at capacity
        while len(self._store) >= self._max_size:
            evicted_key, _ = self._store.popitem(last=False)
            logger.debug("cache_evicted", key=evicted_key[:12])

        self._store[key] = CacheEntry(response=response)

    def clear(self) -> None:
        self._store.clear()
        self._hits = 0
        self._misses = 0

    @property
    def stats(self) -> dict:
        total = self._hits + self._misses
        return {
            "size": len(self._store),
            "max_size": self._max_size,
            "ttl": self._ttl,
            "hits": self._hits,
            "misses": self._misses,
            "hit_rate": round(self._hits / total, 3) if total > 0 else 0.0,
        }


# Module-level singleton
extraction_cache = ExtractionCache()
