"""
Redis-based cache manager for Media Downloader API
Provides caching for expensive queries with configurable TTL
"""
import json
import sys
from pathlib import Path
from typing import Any, Optional, Callable
from functools import wraps
import redis
from redis.exceptions import RedisError

# Add parent path to allow imports from modules
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from modules.universal_logger import get_logger
from web.backend.core.config import settings

logger = get_logger('CacheManager')


class CacheManager:
    """Redis cache manager with automatic connection handling"""

    def __init__(self, host: str = '127.0.0.1', port: int = 6379, db: int = 0, ttl: int = 300):
        """
        Initialize cache manager

        Args:
            host: Redis host (default: 127.0.0.1)
            port: Redis port (default: 6379)
            db: Redis database number (default: 0)
            ttl: Default TTL in seconds (default: 300 = 5 minutes)
        """
        self.host = host
        self.port = port
        self.db = db
        self.default_ttl = ttl
        self._redis = None
        self._connect()

    def _connect(self):
        """Connect to Redis with error handling"""
        try:
            self._redis = redis.Redis(
                host=self.host,
                port=self.port,
                db=self.db,
                decode_responses=True,
                socket_connect_timeout=2,
                socket_timeout=2
            )
            # Test connection
            self._redis.ping()
            logger.info(f"Connected to Redis at {self.host}:{self.port}", module="Redis")
        except RedisError as e:
            logger.warning(f"Redis connection failed: {e}. Caching disabled.", module="Redis")
            self._redis = None

    @property
    def is_available(self) -> bool:
        """Check if Redis is available"""
        if self._redis is None:
            return False
        try:
            self._redis.ping()
            return True
        except RedisError:
            return False

    def get(self, key: str) -> Optional[Any]:
        """
        Get value from cache

        Args:
            key: Cache key

        Returns:
            Cached value (deserialized from JSON) or None if not found/error
        """
        if not self.is_available:
            return None

        try:
            value = self._redis.get(key)
            if value is None:
                return None
            return json.loads(value)
        except (RedisError, json.JSONDecodeError) as e:
            logger.warning(f"Cache get error for key '{key}': {e}", module="Cache")
            return None

    def set(self, key: str, value: Any, ttl: Optional[int] = None):
        """
        Set value in cache

        Args:
            key: Cache key
            value: Value to cache (will be JSON serialized)
            ttl: TTL in seconds (default: use default_ttl)
        """
        if not self.is_available:
            return

        ttl = ttl if ttl is not None else self.default_ttl

        try:
            serialized = json.dumps(value)
            self._redis.setex(key, ttl, serialized)
        except (RedisError, TypeError) as e:
            logger.warning(f"Cache set error for key '{key}': {e}", module="Cache")

    def delete(self, key: str):
        """
        Delete key from cache

        Args:
            key: Cache key to delete
        """
        if not self.is_available:
            return

        try:
            self._redis.delete(key)
        except RedisError as e:
            logger.warning(f"Cache delete error for key '{key}': {e}", module="Cache")

    def clear(self, pattern: str = "*"):
        """
        Clear cache keys matching pattern

        Args:
            pattern: Redis key pattern (default: "*" clears all)
        """
        if not self.is_available:
            return

        try:
            keys = self._redis.keys(pattern)
            if keys:
                self._redis.delete(*keys)
                logger.info(f"Cleared {len(keys)} cache keys matching '{pattern}'", module="Cache")
        except RedisError as e:
            logger.warning(f"Cache clear error for pattern '{pattern}': {e}", module="Cache")

    def cached(self, key_prefix: str, ttl: Optional[int] = None):
        """
        Decorator for caching function results

        Args:
            key_prefix: Prefix for cache key (full key includes function args)
            ttl: TTL in seconds (default: use default_ttl)

        Example:
            @cache_manager.cached('stats', ttl=300)
            def get_download_stats(platform: str, days: int):
                # Expensive query
                return stats
        """
        def decorator(func: Callable) -> Callable:
            @wraps(func)
            async def async_wrapper(*args, **kwargs):
                # Build cache key from function args
                cache_key = f"{key_prefix}:{func.__name__}:{hash((args, tuple(sorted(kwargs.items()))))}"

                # Try to get from cache
                cached_result = self.get(cache_key)
                if cached_result is not None:
                    logger.debug(f"Cache HIT: {cache_key}", module="Cache")
                    return cached_result

                # Cache miss - execute function
                logger.debug(f"Cache MISS: {cache_key}", module="Cache")
                result = await func(*args, **kwargs)

                # Store in cache
                self.set(cache_key, result, ttl)

                return result

            @wraps(func)
            def sync_wrapper(*args, **kwargs):
                # Build cache key from function args
                cache_key = f"{key_prefix}:{func.__name__}:{hash((args, tuple(sorted(kwargs.items()))))}"

                # Try to get from cache
                cached_result = self.get(cache_key)
                if cached_result is not None:
                    logger.debug(f"Cache HIT: {cache_key}", module="Cache")
                    return cached_result

                # Cache miss - execute function
                logger.debug(f"Cache MISS: {cache_key}", module="Cache")
                result = func(*args, **kwargs)

                # Store in cache
                self.set(cache_key, result, ttl)

                return result

            # Return appropriate wrapper based on function type
            import asyncio
            if asyncio.iscoroutinefunction(func):
                return async_wrapper
            else:
                return sync_wrapper

        return decorator


# Global cache manager instance (use centralized config)
cache_manager = CacheManager(
    host=settings.REDIS_HOST,
    port=settings.REDIS_PORT,
    db=settings.REDIS_DB,
    ttl=settings.REDIS_TTL
)


def invalidate_download_cache():
    """Invalidate all download-related caches"""
    cache_manager.clear("downloads:*")
    cache_manager.clear("stats:*")
    cache_manager.clear("filters:*")
    logger.info("Invalidated download-related caches", module="Cache")
