"""Deterministic caching with SHA256 content hashing."""

import hashlib
import json
from abc import ABC, abstractmethod
from typing import Optional

from .models import LLMRequest, LLMResponse


class CacheBackend(ABC):
    """Abstract cache backend."""
    
    @abstractmethod
    def get(self, key: str) -> Optional[dict]:
        """Get cached value."""
        pass
    
    @abstractmethod
    def set(self, key: str, value: dict, ttl: Optional[int] = None) -> None:
        """Set cached value."""
        pass
    
    @abstractmethod
    def exists(self, key: str) -> bool:
        """Check if key exists."""
        pass
    
    @abstractmethod
    def delete(self, key: str) -> None:
        """Delete key."""
        pass
    
    @abstractmethod
    def clear(self) -> None:
        """Clear all cache."""
        pass


class InMemoryCache(CacheBackend):
    """Simple in-memory cache for development."""
    
    def __init__(self):
        self._cache: dict[str, dict] = {}
    
    def get(self, key: str) -> Optional[dict]:
        return self._cache.get(key)
    
    def set(self, key: str, value: dict, ttl: Optional[int] = None) -> None:
        self._cache[key] = value
    
    def exists(self, key: str) -> bool:
        return key in self._cache
    
    def delete(self, key: str) -> None:
        self._cache.pop(key, None)
    
    def clear(self) -> None:
        self._cache.clear()


class RedisCache(CacheBackend):
    """Redis cache backend for production."""
    
    def __init__(self, redis_url: str = "redis://localhost:6379/0"):
        import redis
        self._redis = redis.from_url(redis_url, decode_responses=False)
    
    def get(self, key: str) -> Optional[dict]:
        data = self._redis.get(key)
        if data:
            return json.loads(data)
        return None
    
    def set(self, key: str, value: dict, ttl: Optional[int] = None) -> None:
        data = json.dumps(value)
        if ttl:
            self._redis.setex(key, ttl, data)
        else:
            self._redis.set(key, data)
    
    def exists(self, key: str) -> bool:
        return bool(self._redis.exists(key))
    
    def delete(self, key: str) -> None:
        self._redis.delete(key)
    
    def clear(self) -> None:
        self._redis.flushdb()


class DeterministicCache:
    """Deterministic cache using SHA256 content hashing."""
    
    def __init__(
        self, 
        backend: CacheBackend,
        ttl: Optional[int] = None,
        key_prefix: str = "llm:cache:"
    ):
        self.backend = backend
        self.ttl = ttl
        self.key_prefix = key_prefix
        self._hits = 0
        self._misses = 0
    
    def generate_cache_key(self, request: LLMRequest) -> str:
        """Generate deterministic SHA256 cache key from request."""
        normalized = request.normalize_for_cache_key()
        hash_digest = hashlib.sha256(normalized.encode('utf-8')).hexdigest()
        return f"{self.key_prefix}{hash_digest}"
    
    def get(self, request: LLMRequest) -> Optional[LLMResponse]:
        """Get cached response for request."""
        cache_key = self.generate_cache_key(request)
        
        cached_data = self.backend.get(cache_key)
        if cached_data:
            self._hits += 1
            response = LLMResponse(**cached_data)
            response.from_cache = True
            response.cache_key = cache_key
            return response
        
        self._misses += 1
        return None
    
    def set(self, request: LLMRequest, response: LLMResponse) -> str:
        """Cache response for request."""
        cache_key = self.generate_cache_key(request)
        
        # Store response data
        cache_data = response.model_dump(mode='json')
        self.backend.set(cache_key, cache_data, ttl=self.ttl)
        
        return cache_key
    
    def exists(self, request: LLMRequest) -> bool:
        """Check if request is cached."""
        cache_key = self.generate_cache_key(request)
        return self.backend.exists(cache_key)
    
    def invalidate(self, request: LLMRequest) -> None:
        """Invalidate cache for specific request."""
        cache_key = self.generate_cache_key(request)
        self.backend.delete(cache_key)
    
    def clear(self) -> None:
        """Clear all cache."""
        self.backend.clear()
        self._hits = 0
        self._misses = 0
    
    @property
    def hit_rate(self) -> float:
        """Calculate cache hit rate."""
        total = self._hits + self._misses
        if total == 0:
            return 0.0
        return self._hits / total
    
    @property
    def stats(self) -> dict:
        """Get cache statistics."""
        return {
            "hits": self._hits,
            "misses": self._misses,
            "hit_rate": self.hit_rate,
            "total_requests": self._hits + self._misses
        }
