"""
Sistema de caching con Redis y memoria
"""
import json
import hashlib
import logging
from functools import wraps
from typing import Optional, Any, Callable
from datetime import datetime, timedelta
import redis
from redis.exceptions import RedisError

from app.core.config import settings

logger = logging.getLogger(__name__)


class CacheClient:
    """Cliente unificado de caché"""
    
    def __init__(self):
        self.redis_client = None
        self.memory_cache = {}  # Fallback en memoria
        self.enabled = settings.cache_enabled
        
        if self.enabled:
            try:
                self.redis_client = redis.Redis(
                    host=settings.redis_host,
                    port=settings.redis_port,
                    db=settings.redis_db,
                    password=settings.redis_password,
                    decode_responses=True,
                    socket_connect_timeout=5,
                    socket_timeout=5
                )
                # Probar conexión
                self.redis_client.ping()
                logger.info(f"✅ Redis conectado en {settings.redis_host}:{settings.redis_port}")
            except RedisError as e:
                logger.warning(f"⚠️ Redis no disponible: {e}. Usando caché en memoria.")
                self.redis_client = None
                self.enabled = False
    
    def _get_key(self, prefix: str, key: str) -> str:
        """Genera clave con prefijo"""
        return f"{prefix}:{key}"
    
    def _hash_key(self, key: str) -> str:
        """Hash de claves largas"""
        if len(key) > 100:
            return hashlib.md5(key.encode()).hexdigest()
        return key
    
    async def get(self, prefix: str, key: str) -> Optional[Any]:
        """Obtener valor del caché"""
        if not self.enabled:
            return None
        
        cache_key = self._get_key(prefix, self._hash_key(key))
        
        try:
            # Intentar Redis primero
            if self.redis_client:
                value = self.redis_client.get(cache_key)
                if value:
                    return json.loads(value)
            
            # Fallback a memoria
            if cache_key in self.memory_cache:
                data, expiry = self.memory_cache[cache_key]
                if datetime.now() < expiry:
                    return data
                else:
                    del self.memory_cache[cache_key]
            
            return None
        except Exception as e:
            logger.error(f"Error reading cache: {e}")
            return None
    
    async def set(self, prefix: str, key: str, value: Any, ttl: int = 60):
        """Guardar en caché"""
        if not self.enabled:
            return
        
        cache_key = self._get_key(prefix, self._hash_key(key))
        
        try:
            serialized = json.dumps(value, default=str)
            
            if self.redis_client:
                self.redis_client.setex(cache_key, ttl, serialized)
            
            # Guardar también en memoria
            self.memory_cache[cache_key] = (value, datetime.now() + timedelta(seconds=ttl))
        except Exception as e:
            logger.error(f"Error writing cache: {e}")
    
    async def delete(self, prefix: str, key: str):
        """Eliminar del caché"""
        if not self.enabled:
            return
        
        cache_key = self._get_key(prefix, self._hash_key(key))
        
        try:
            if self.redis_client:
                self.redis_client.delete(cache_key)
            
            if cache_key in self.memory_cache:
                del self.memory_cache[cache_key]
        except Exception as e:
            logger.error(f"Error deleting cache: {e}")
    
    async def delete_pattern(self, pattern: str):
        """Eliminar por patrón"""
        if not self.enabled or not self.redis_client:
            return
        
        try:
            keys = self.redis_client.keys(pattern)
            if keys:
                self.redis_client.delete(*keys)
        except Exception as e:
            logger.error(f"Error deleting pattern: {e}")
    
    async def clear_all(self):
        """Limpiar todo el caché"""
        if not self.enabled:
            return
        
        try:
            if self.redis_client:
                self.redis_client.flushdb()
            self.memory_cache.clear()
            logger.info("Cache cleared")
        except Exception as e:
            logger.error(f"Error clearing cache: {e}")


# Singleton
cache_client = CacheClient()


def cached(prefix: str, ttl: int = 60, key_builder: Optional[Callable] = None):
    """
    Decorador para cachear respuestas de endpoints
    
    Uso:
        @cached(prefix="eta", ttl=30)
        async def get_eta(address_id: int):
            ...
    """
    def decorator(func: Callable):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            if not cache_client.enabled:
                return await func(*args, **kwargs)
            
            # Construir clave
            if key_builder:
                cache_key = key_builder(*args, **kwargs)
            else:
                # Usar nombre de función y argumentos
                cache_key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
            
            # Intentar obtener del caché
            cached_value = await cache_client.get(prefix, cache_key)
            if cached_value is not None:
                logger.debug(f"Cache HIT: {prefix}:{cache_key}")
                return cached_value
            
            # Ejecutar función
            result = await func(*args, **kwargs)
            
            # Guardar en caché
            if result is not None:
                await cache_client.set(prefix, cache_key, result, ttl)
                logger.debug(f"Cache MISS: {prefix}:{cache_key} saved")
            
            return result
        return wrapper
    return decorator


async def invalidate_user_cache(user_id: int):
    """Invalidar caché relacionada con un usuario"""
    await cache_client.delete_pattern(f"addresses:user:{user_id}:*")
    await cache_client.delete_pattern(f"eta:user:{user_id}:*")
    logger.info(f"Cache invalidated for user {user_id}")


async def invalidate_route_cache(route_id: str):
    """Invalidar caché relacionada con una ruta"""
    await cache_client.delete_pattern(f"eta:route:{route_id}:*")
    await cache_client.delete_pattern(f"truck_status:{route_id}")
    logger.info(f"Cache invalidated for route {route_id}")