import json
import hashlib
from functools import wraps
from typing import Any, Callable, Optional
from app.core.redis import get_redis
import pickle

def generate_cache_key(func_name: str, args: tuple, kwargs: dict) -> str:
    """Generate a unique cache key based on function name and arguments."""
    # Create a string representation of args and kwargs
    key_data = {
        'function': func_name,
        'args': str(args),
        'kwargs': str(sorted(kwargs.items()))
    }
    key_string = json.dumps(key_data, sort_keys=True)
    # Use SHA256 hash for consistent, shorter keys
    return f"cache:{hashlib.sha256(key_string.encode()).hexdigest()}"

def cache(expire_time: int = 3600, key_prefix: Optional[str] = None):
    """
    Decorator to cache function results in Redis.
    
    Args:
        expire_time: Expiration time in seconds (default: 1 hour)
        key_prefix: Optional prefix for cache keys
    """
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def wrapper(*args, **kwargs) -> Any:
            redis_client = await get_redis()
            
            # Generate cache key
            cache_key = generate_cache_key(func.__name__, args, kwargs)
            if key_prefix:
                cache_key = f"{key_prefix}:{cache_key}"
            
            try:
                # Try to get from cache
                cached_result = await redis_client.get(cache_key)
                if cached_result:
                    # Deserialize and return cached result
                    return pickle.loads(cached_result)
                
                # Cache miss - execute function
                result = await func(*args, **kwargs)
                
                # Store result in cache
                serialized_result = pickle.dumps(result)
                await redis_client.setex(cache_key, expire_time, serialized_result)
                
                return result
                
            except Exception as e:
                # If caching fails, still execute the function
                print(f"Cache error: {e}")
                return await func(*args, **kwargs)
                
        return wrapper
    return decorator

async def invalidate_cache_pattern(pattern: str):
    """Invalidate all cache keys matching a pattern."""
    redis_client = await get_redis()
    try:
        keys = await redis_client.keys(pattern)
        if keys:
            await redis_client.delete(*keys)
    except Exception as e:
        print(f"Cache invalidation error: {e}")

async def clear_all_cache():
    """Clear all cache entries."""
    redis_client = await get_redis()
    try:
        await redis_client.flushdb()
    except Exception as e:
        print(f"Cache clear error: {e}") 