from redis import Redis
from typing import Optional, Any
import pickle
import logging
from functools import wraps

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class RedisConnectionError(Exception):
    """Custom exception for Redis connection issues"""

    pass


class RedisManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(RedisManager, cls).__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if not self._initialized:
            try:
                self.client = Redis(
                    host="localhost",
                    port=6380,
                    db=0,
                    decode_responses=False,
                    socket_timeout=5,
                )
                self._initialized = True
                logger.info("Redis connection established")
            except Exception as e:
                logger.error(f"Failed to connect to Redis: {str(e)}")
                raise RedisConnectionError(f"Redis connection failed: {str(e)}")

        self.default_ttl = 1800

    def _handle_connection(func):
        """Decorator to handle Redis connection errors"""

        @wraps(func)
        def wrapper(self, *args, **kwargs):
            try:
                return func(self, *args, **kwargs)
            except Exception as e:
                logger.error(f"Redis operation failed: {str(e)}")
                raise RedisConnectionError(f"Redis operation failed: {str(e)}")

        return wrapper

    @_handle_connection
    def set_data(self, key: str, value: Any, expiry: int = 1800) -> bool:
        """Store data in Redis with expiry time"""
        try:
            pickled_value = pickle.dumps(value)
            return self.client.set(key, pickled_value, ex=expiry)
        except Exception as e:
            logger.error(f"Failed to set data for key {key}: {str(e)}")
            return False

    @_handle_connection
    def get_data(self, key: str) -> Optional[Any]:
        """Retrieve data from Redis"""
        try:
            data = self.client.get(key)
            return pickle.loads(data) if data else None
        except Exception as e:
            logger.error(f"Failed to get data for key {key}: {str(e)}")
            return None

    @_handle_connection
    def delete_data(self, key: str) -> bool:
        """Delete data from Redis"""
        try:
            return bool(self.client.delete(key))
        except Exception as e:
            logger.error(f"Failed to delete key {key}: {str(e)}")
            return False

    @_handle_connection
    def clear_user_data(self, user_id: str) -> bool:
        """Clear all data for a specific user"""
        try:
            pattern = f"user:{user_id}:*"
            keys = self.client.keys(pattern)
            if keys:
                return bool(self.client.delete(*keys))
            return True
        except Exception as e:
            logger.error(f"Failed to clear data for user {user_id}: {str(e)}")
            return False

    @_handle_connection
    def get_memory_usage(self) -> dict:
        """Get Redis memory statistics"""
        try:
            info = self.client.info(section="memory")
            return {
                "used_memory": info["used_memory_human"],
                "peak_memory": info["used_memory_peak_human"],
                "fragmentation": info["mem_fragmentation_ratio"],
            }
        except Exception as e:
            logger.error(f"Failed to get memory usage: {str(e)}")
            return {}

    @_handle_connection
    def refresh_user_ttl(self, user_id: str) -> bool:
        """Refresh TTL for all keys belonging to a user"""
        try:
            # Get all keys for this user
            pattern = f"user:{user_id}:*"
            user_keys = self.client.keys(pattern)

            if not user_keys:
                return False

            # Update TTL for all user's keys
            pipeline = self.client.pipeline()
            for key in user_keys:
                pipeline.expire(key, self.default_ttl)

            # Execute all EXPIRE commands atomically
            results = pipeline.execute()

            # Check if all operations succeeded
            success = all(results)
            if not success:
                logger.warning(f"Some TTL updates failed for user {user_id}")

            return success

        except Exception as e:
            logger.error(f"Failed to refresh TTL for user {user_id}: {str(e)}")
            return False

    @_handle_connection
    def refresh_key_ttl(self, key: str, ttl: int = None) -> bool:
        """Refresh TTL for a specific key"""
        try:
            return self.client.expire(key, ttl or self.default_ttl)
        except Exception as e:
            logger.error(f"Failed to refresh TTL for key {key}: {str(e)}")
            return False

    def is_connected(self) -> bool:
        """Check if Redis connection is alive"""
        try:
            return self.client.ping()
        except Exception:
            return False

    def get_keys_by_pattern(self, pattern: str = "*") -> list:
        """Get all keys matching pattern"""
        try:
            return [key.decode("utf-8") for key in self.client.keys(pattern)]
        except Exception as e:
            logger.error(f"Error getting keys: {e}")
            return []

    def get_key_info(self, key: str) -> dict:
        """Get detailed information about a key"""
        try:
            return {
                "type": self.client.type(key).decode("utf-8"),
                "ttl": self.client.ttl(key),
                "memory": self.client.memory_usage(key),
            }
        except Exception as e:
            logger.error(f"Error getting key info: {e}")
            return {}

    def monitor_user_data(self, user_id: str) -> dict:
        """Monitor all data for a specific user"""
        try:
            user_keys = self.get_keys_by_pattern(f"user:{user_id}:*")
            return {
                "total_keys": len(user_keys),
                "keys": {key: self.get_key_info(key) for key in user_keys},
                "memory_usage": self.get_memory_usage(),
            }
        except Exception as e:
            logger.error(f"Error monitoring user data: {e}")
            return {}
