"""

Cache manager for Studio with Redis support and in-memory fallback.

Caches LLM responses to save costs.

"""



import hashlib

import json

import logging

from typing import Optional, Any

from datetime import datetime, timedelta



logger = logging.getLogger(__name__)





class CacheManager:

    """

    Manages caching of LLM responses.

    Uses Redis if available, falls back to in-memory dict.

    """



    def __init__(self, use_redis: bool = True, redis_host: str = "localhost", redis_port: int = 6379):

        self.use_redis = use_redis

        self.redis_client = None

        self.memory_cache = {}  # Fallback in-memory cache

        self.cache_hits = 0

        self.cache_misses = 0



        if use_redis:

            try:

                import redis

                self.redis_client = redis.Redis(

                    host=redis_host,

                    port=redis_port,

                    db=0,

                    decode_responses=True,

                    socket_connect_timeout=2

                )

                # Test connection

                self.redis_client.ping()

                logger.info("✓ Redis cache connected")

            except Exception as e:

                logger.warning(f"Redis not available, using in-memory cache: {e}")

                self.redis_client = None

                self.use_redis = False



    def _generate_cache_key(self, provider: str, model: str, messages: list) -> str:

        """Generate a deterministic cache key from request parameters."""

        # Create a string representation of the request

        cache_data = {

            "provider": provider,

            "model": model,

            "messages": messages

        }

        cache_string = json.dumps(cache_data, sort_keys=True)



        # Hash it for a compact key

        key_hash = hashlib.sha256(cache_string.encode()).hexdigest()[:16]

        return f"studio:cache:{provider}:{key_hash}"



    def get(self, provider: str, model: str, messages: list) -> Optional[dict]:

        """

        Retrieve cached response if available.



        Returns:

            Cached response dict or None

        """

        cache_key = self._generate_cache_key(provider, model, messages)



        try:

            if self.redis_client:

                # Try Redis

                cached = self.redis_client.get(cache_key)

                if cached:

                    self.cache_hits += 1

                    logger.info(f"Cache HIT for {provider}/{model}")

                    return json.loads(cached)

            else:

                # Try in-memory cache

                if cache_key in self.memory_cache:

                    cached_data, expires_at = self.memory_cache[cache_key]

                    if datetime.now() < expires_at:

                        self.cache_hits += 1

                        logger.info(f"Cache HIT (memory) for {provider}/{model}")

                        return cached_data

                    else:

                        # Expired, remove it

                        del self.memory_cache[cache_key]



        except Exception as e:

            logger.warning(f"Cache get error: {e}")



        self.cache_misses += 1

        logger.info(f"Cache MISS for {provider}/{model}")

        return None



    def set(self, provider: str, model: str, messages: list, response: dict, ttl: int = 3600):

        """

        Cache a response.



        Args:

            provider: Provider name

            model: Model name

            messages: Request messages

            response: Response to cache

            ttl: Time to live in seconds (default 1 hour)

        """

        cache_key = self._generate_cache_key(provider, model, messages)



        try:

            if self.redis_client:

                # Store in Redis

                self.redis_client.setex(

                    cache_key,

                    ttl,

                    json.dumps(response)

                )

                logger.info(f"Cached response for {provider}/{model} (TTL: {ttl}s)")

            else:

                # Store in memory

                expires_at = datetime.now() + timedelta(seconds=ttl)

                self.memory_cache[cache_key] = (response, expires_at)

                logger.info(f"Cached response in memory for {provider}/{model}")



        except Exception as e:

            logger.warning(f"Cache set error: {e}")



    def get_stats(self) -> dict:

        """Get cache statistics."""

        total_requests = self.cache_hits + self.cache_misses

        hit_rate = (self.cache_hits / total_requests * 100) if total_requests > 0 else 0



        stats = {

            "hits": self.cache_hits,

            "misses": self.cache_misses,

            "total": total_requests,

            "hit_rate": f"{hit_rate:.1f}%",

            "backend": "redis" if self.redis_client else "memory"

        }



        if not self.redis_client:

            stats["memory_cache_size"] = len(self.memory_cache)



        return stats



    def clear(self):

        """Clear all cached data."""

        if self.redis_client:

            # Clear all studio cache keys

            try:

                keys = self.redis_client.keys("studio:cache:*")

                if keys:

                    self.redis_client.delete(*keys)

                    logger.info(f"Cleared {len(keys)} cache entries from Redis")

            except Exception as e:

                logger.warning(f"Cache clear error: {e}")

        else:

            self.memory_cache.clear()

            logger.info("Cleared memory cache")



        # Reset stats

        self.cache_hits = 0

        self.cache_misses = 0

