import redis
import json
import pickle
from typing import Any, Optional, Union, Dict, List
from datetime import timedelta
import os
import logging
from functools import wraps
import hashlib

logger = logging.getLogger(__name__)

class CacheService:
    """Service for Redis caching operations"""
    
    def __init__(self, redis_url: str = None):
        """Initialize Redis connection"""
        self.redis_url = redis_url or os.getenv('REDIS_URL', 'redis://localhost:6379/0')
        self.redis = None
        self._connect()
    
    def _connect(self):
        """Establish Redis connection"""
        try:
            self.redis = redis.from_url(self.redis_url, decode_responses=False)
            # Test connection
            self.redis.ping()
            logger.info("Redis connection established successfully")
        except Exception as e:
            logger.error(f"Failed to connect to Redis: {e}")
            self.redis = None
    
    def _is_connected(self) -> bool:
        """Check if Redis is connected"""
        if not self.redis:
            return False
        try:
            self.redis.ping()
            return True
        except:
            return False
    
    def get(self, key: str, default: Any = None) -> Any:
        """Get value from cache"""
        if not self._is_connected():
            return default
        
        try:
            value = self.redis.get(key)
            if value is None:
                return default
            return pickle.loads(value)
        except Exception as e:
            logger.error(f"Error getting cache key {key}: {e}")
            return default
    
    def set(self, key: str, value: Any, expire: int = 3600) -> bool:
        """Set value in cache with expiration"""
        if not self._is_connected():
            return False
        
        try:
            serialized_value = pickle.dumps(value)
            return self.redis.setex(key, expire, serialized_value)
        except Exception as e:
            logger.error(f"Error setting cache key {key}: {e}")
            return False
    
    def delete(self, key: str) -> bool:
        """Delete key from cache"""
        if not self._is_connected():
            return False
        
        try:
            return bool(self.redis.delete(key))
        except Exception as e:
            logger.error(f"Error deleting cache key {key}: {e}")
            return False
    
    def exists(self, key: str) -> bool:
        """Check if key exists in cache"""
        if not self._is_connected():
            return False
        
        try:
            return bool(self.redis.exists(key))
        except Exception as e:
            logger.error(f"Error checking cache key {key}: {e}")
            return False
    
    def expire(self, key: str, seconds: int) -> bool:
        """Set expiration for key"""
        if not self._is_connected():
            return False
        
        try:
            return bool(self.redis.expire(key, seconds))
        except Exception as e:
            logger.error(f"Error setting expiration for cache key {key}: {e}")
            return False
    
    def ttl(self, key: str) -> int:
        """Get time to live for key"""
        if not self._is_connected():
            return -1
        
        try:
            return self.redis.ttl(key)
        except Exception as e:
            logger.error(f"Error getting TTL for cache key {key}: {e}")
            return -1
    
    def clear_pattern(self, pattern: str) -> int:
        """Clear all keys matching pattern"""
        if not self._is_connected():
            return 0
        
        try:
            keys = self.redis.keys(pattern)
            if keys:
                return self.redis.delete(*keys)
            return 0
        except Exception as e:
            logger.error(f"Error clearing cache pattern {pattern}: {e}")
            return 0
    
    def clear_all(self) -> bool:
        """Clear all cache"""
        if not self._is_connected():
            return False
        
        try:
            self.redis.flushdb()
            return True
        except Exception as e:
            logger.error(f"Error clearing all cache: {e}")
            return False
    
    def get_many(self, keys: List[str]) -> Dict[str, Any]:
        """Get multiple values from cache"""
        if not self._is_connected():
            return {}
        
        try:
            values = self.redis.mget(keys)
            result = {}
            for key, value in zip(keys, values):
                if value is not None:
                    result[key] = pickle.loads(value)
            return result
        except Exception as e:
            logger.error(f"Error getting multiple cache keys: {e}")
            return {}
    
    def set_many(self, data: Dict[str, Any], expire: int = 3600) -> bool:
        """Set multiple values in cache"""
        if not self._is_connected():
            return False
        
        try:
            pipeline = self.redis.pipeline()
            for key, value in data.items():
                serialized_value = pickle.dumps(value)
                pipeline.setex(key, expire, serialized_value)
            pipeline.execute()
            return True
        except Exception as e:
            logger.error(f"Error setting multiple cache keys: {e}")
            return False
    
    def increment(self, key: str, amount: int = 1) -> Optional[int]:
        """Increment counter in cache"""
        if not self._is_connected():
            return None
        
        try:
            return self.redis.incr(key, amount)
        except Exception as e:
            logger.error(f"Error incrementing cache key {key}: {e}")
            return None
    
    def decrement(self, key: str, amount: int = 1) -> Optional[int]:
        """Decrement counter in cache"""
        if not self._is_connected():
            return None
        
        try:
            return self.redis.decr(key, amount)
        except Exception as e:
            logger.error(f"Error decrementing cache key {key}: {e}")
            return None

# Global cache instance
cache_service = CacheService()

def cache_key_generator(*args, **kwargs) -> str:
    """Generate cache key from function arguments"""
    # Create a hash of the arguments
    key_data = str(args) + str(sorted(kwargs.items()))
    return hashlib.md5(key_data.encode()).hexdigest()

def cached(expire: int = 3600, key_prefix: str = ""):
    """Decorator for caching function results"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Generate cache key
            func_key = f"{key_prefix}:{func.__name__}:{cache_key_generator(*args, **kwargs)}"
            
            # Try to get from cache
            cached_result = cache_service.get(func_key)
            if cached_result is not None:
                logger.debug(f"Cache hit for {func_key}")
                return cached_result
            
            # Execute function and cache result
            result = func(*args, **kwargs)
            cache_service.set(func_key, result, expire)
            logger.debug(f"Cache miss for {func_key}, stored result")
            
            return result
        return wrapper
    return decorator

def invalidate_cache_pattern(pattern: str):
    """Decorator to invalidate cache after function execution"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            cache_service.clear_pattern(pattern)
            logger.debug(f"Invalidated cache pattern: {pattern}")
            return result
        return wrapper
    return decorator

# Cache key constants
class CacheKeys:
    """Constants for cache keys"""
    MILITANTE_LIST = "militantes:list"
    MILITANTE_DETAIL = "militante:detail:{}"
    PAGAMENTO_LIST = "pagamentos:list"
    PAGAMENTO_DETAIL = "pagamento:detail:{}"
    COTA_LIST = "cotas:list"
    COTA_DETAIL = "cota:detail:{}"
    DASHBOARD_STATS = "dashboard:stats"
    USER_SESSION = "user:session:{}"
    API_RESPONSE = "api:response:{}"
    
    @staticmethod
    def militante_detail(militante_id: int) -> str:
        return CacheKeys.MILITANTE_DETAIL.format(militante_id)
    
    @staticmethod
    def pagamento_detail(pagamento_id: int) -> str:
        return CacheKeys.PAGAMENTO_DETAIL.format(pagamento_id)
    
    @staticmethod
    def cota_detail(cota_id: int) -> str:
        return CacheKeys.COTA_DETAIL.format(cota_id)
    
    @staticmethod
    def user_session(user_id: int) -> str:
        return CacheKeys.USER_SESSION.format(user_id)
    
    @staticmethod
    def api_response(endpoint: str) -> str:
        return CacheKeys.API_RESPONSE.format(endpoint) 