"""
=========================================
CV6 Autonomous Engine — AI Response Cache
Prevents duplicate AI calls for the same
symbol within the configured TTL window.
Key: (symbol, signal)  Value: AIResult
=========================================
"""

import time
from dataclasses import dataclass, field
from threading import Lock
from typing import Dict, Optional


@dataclass
class CachedAIResult:
    symbol:     str
    signal:     str          # BUY | SELL | HOLD
    confidence: float        # 0-100
    reason:     str
    models_used: list
    cached_at:  float = field(default_factory=time.time)

    def is_valid(self, ttl_seconds: int) -> bool:
        return (time.time() - self.cached_at) < ttl_seconds


class AICache:
    """
    Thread-safe in-memory AI response cache.
    - Keyed by (symbol, timeframe)
    - TTL configurable per instance
    - Auto-prunes expired entries
    """

    def __init__(self, ttl_seconds: int = 300):
        self._ttl    = ttl_seconds
        self._store: Dict[str, CachedAIResult] = {}
        self._lock   = Lock()
        self._hits   = 0
        self._misses = 0

    # ── Public API ────────────────────────────────────────────────────────────

    def get(self, symbol: str, timeframe: str = "1d") -> Optional[CachedAIResult]:
        key = self._key(symbol, timeframe)
        with self._lock:
            entry = self._store.get(key)
            if entry and entry.is_valid(self._ttl):
                self._hits += 1
                return entry
            if entry:
                del self._store[key]   # expired
            self._misses += 1
            return None

    def put(self, symbol: str, result: CachedAIResult, timeframe: str = "1d") -> None:
        key = self._key(symbol, timeframe)
        with self._lock:
            self._store[key] = result

    def invalidate(self, symbol: str, timeframe: str = "1d") -> None:
        key = self._key(symbol, timeframe)
        with self._lock:
            self._store.pop(key, None)

    def clear(self) -> None:
        with self._lock:
            self._store.clear()

    def prune(self) -> int:
        """Remove all expired entries. Returns count removed."""
        with self._lock:
            expired = [k for k, v in self._store.items() if not v.is_valid(self._ttl)]
            for k in expired:
                del self._store[k]
            return len(expired)

    def set_ttl(self, seconds: int) -> None:
        self._ttl = max(10, seconds)

    @property
    def stats(self) -> dict:
        with self._lock:
            total = self._hits + self._misses
            hit_rate = round(self._hits / total * 100, 1) if total > 0 else 0
            return {
                "size":     len(self._store),
                "hits":     self._hits,
                "misses":   self._misses,
                "hit_rate": hit_rate,
                "ttl_s":    self._ttl,
            }

    # ── Internal ──────────────────────────────────────────────────────────────

    @staticmethod
    def _key(symbol: str, timeframe: str) -> str:
        return f"{symbol}::{timeframe}"


# ── Singleton ─────────────────────────────────────────────────────────────────
ai_cache = AICache(ttl_seconds=300)
