"""
Redis client wrapper for MedhaOne Access Control Library.

Provides optional Redis caching with automatic fallback to PostgreSQL.
Designed for Azure Redis Cache with SSL/TLS support.
"""

import redis.asyncio as redis
from typing import Optional, Any, List
import json
import logging
import asyncio
from datetime import datetime, timezone

logger = logging.getLogger(__name__)


class RedisClient:
    """
    Async Redis client wrapper with Azure Redis support.

    Features:
    - Automatic SSL/TLS for Azure Redis Cache
    - Connection pooling
    - Graceful error handling (never breaks main flow)
    - JSON serialization for complex objects
    """

    def __init__(self, config):
        """
        Initialize Redis client from LibraryConfig.

        Args:
            config: LibraryConfig instance with Redis settings
        """
        self.config = config
        self.enabled = config.redis_enabled
        self._client: Optional[redis.Redis] = None
        self._connected = False
        self._shutting_down = False

        if self.enabled:
            self._initialize_client()

    def _initialize_client(self):
        """Initialize Redis connection with Azure SSL support."""
        try:
            # Azure Redis Cache connection parameters
            connection_kwargs = {
                'host': self.config.redis_host,
                'port': self.config.redis_port,
                'password': self.config.redis_password,
                'db': self.config.redis_db,
                'decode_responses': self.config.redis_decode_responses,
                'socket_timeout': self.config.redis_socket_timeout,
                'socket_connect_timeout': self.config.redis_socket_connect_timeout,
                'max_connections': self.config.redis_max_connections,
            }

            # Add SSL for Azure Redis (required for Azure Redis Cache)
            if self.config.redis_ssl:
                connection_kwargs['ssl'] = True
                connection_kwargs['ssl_cert_reqs'] = None  # Azure doesn't require cert validation

            self._client = redis.Redis(**connection_kwargs)
            logger.info(f"Redis client initialized: {self.config.redis_host}:{self.config.redis_port} (SSL: {self.config.redis_ssl})")

        except Exception as e:
            logger.error(f"Failed to initialize Redis client: {e}. Falling back to PostgreSQL only.")
            self.enabled = False
            self._client = None

    def _is_event_loop_running(self) -> bool:
        """
        Check if the asyncio event loop is running and not closing.

        Returns:
            True if loop is available and running, False otherwise
        """
        try:
            loop = asyncio.get_running_loop()
            if loop is None:
                return False

            # Check if loop is closed (works on all event loop types)
            if loop.is_closed():
                return False

            # Check if loop is closing (only available on some event loop types)
            # Windows ProactorEventLoop doesn't have is_closing()
            if hasattr(loop, 'is_closing'):
                return not loop.is_closing()

            return True
        except RuntimeError:
            # No running event loop
            return False

    async def ping(self) -> bool:
        """
        Test Redis connection.

        Returns:
            True if connection successful, False otherwise
        """
        if not self.enabled or not self._client or not self._is_event_loop_running():
            return False
        try:
            await self._client.ping()
            self._connected = True
            return True
        except Exception as e:
            logger.warning(f"Redis ping failed: {e}")
            self._connected = False
            return False

    async def get(self, key: str) -> Optional[str]:
        """
        Get value from Redis.

        Args:
            key: Redis key

        Returns:
            Value as string, or None on error (graceful degradation)
        """
        if not self.enabled or not self._client:
            return None
        try:
            return await self._client.get(key)
        except Exception as e:
            logger.warning(f"Redis GET error for key '{key}': {e}")
            return None

    async def get_json(self, key: str) -> Optional[dict]:
        """
        Get JSON value from Redis.

        Args:
            key: Redis key

        Returns:
            Deserialized dict, or None on error/miss
        """
        value = await self.get(key)
        if value:
            try:
                return json.loads(value)
            except json.JSONDecodeError as e:
                logger.warning(f"Redis JSON decode error for key '{key}': {e}")
        return None

    async def set(self, key: str, value: str, ttl: Optional[int] = None) -> bool:
        """
        Set value in Redis with optional TTL.

        Args:
            key: Redis key
            value: Value to store
            ttl: Time to live in seconds (optional)

        Returns:
            True if successful, False on error
        """
        if not self.enabled or not self._client:
            return False
        try:
            if ttl:
                await self._client.setex(key, ttl, value)
            else:
                await self._client.set(key, value)
            return True
        except Exception as e:
            logger.warning(f"Redis SET error for key '{key}': {e}")
            return False

    async def set_json(self, key: str, value: dict, ttl: Optional[int] = None) -> bool:
        """
        Set JSON value in Redis with optional TTL.

        Args:
            key: Redis key
            value: Dict to serialize and store
            ttl: Time to live in seconds (optional)

        Returns:
            True if successful, False on error
        """
        try:
            # default=str handles datetime and other non-JSON-serializable types
            json_str = json.dumps(value, default=str)
            return await self.set(key, json_str, ttl)
        except Exception as e:
            logger.warning(f"Redis SET JSON error for key '{key}': {e}")
            return False

    async def delete(self, *keys: str) -> bool:
        """
        Delete one or more keys from Redis.

        Args:
            *keys: One or more Redis keys to delete

        Returns:
            True if successful, False on error
        """
        if not self.enabled or not self._client or not keys or self._shutting_down:
            return False

        # Check if event loop is still running before attempting deletion
        if not self._is_event_loop_running():
            logger.debug(f"Skipping Redis DELETE for keys {keys}: Event loop is not running")
            return False

        try:
            await self._client.delete(*keys)
            return True
        except RuntimeError as e:
            # Handle "Event loop is closed" gracefully
            if "Event loop is closed" in str(e):
                logger.debug(f"Redis DELETE skipped for keys {keys}: Event loop closed during shutdown")
            else:
                logger.warning(f"Redis DELETE error for keys {keys}: {e}")
            return False
        except Exception as e:
            logger.warning(f"Redis DELETE error for keys {keys}: {e}")
            return False

    async def delete_pattern(self, pattern: str) -> int:
        """
        Delete all keys matching pattern.

        Args:
            pattern: Redis key pattern (e.g., "medha:expr:USER:*")

        Returns:
            Number of keys deleted
        """
        if not self.enabled or not self._client or self._shutting_down:
            return 0

        # Check if event loop is still running before attempting deletion
        if not self._is_event_loop_running():
            logger.debug(f"Skipping Redis DELETE PATTERN for '{pattern}': Event loop is not running")
            return 0

        try:
            keys = []
            async for key in self._client.scan_iter(match=pattern, count=100):
                keys.append(key)

            if keys:
                await self._client.delete(*keys)
                logger.info(f"Deleted {len(keys)} Redis keys matching pattern '{pattern}'")
                return len(keys)
            return 0
        except RuntimeError as e:
            # Handle "Event loop is closed" gracefully
            if "Event loop is closed" in str(e):
                logger.debug(f"Redis DELETE PATTERN skipped for '{pattern}': Event loop closed during shutdown")
            else:
                logger.warning(f"Redis DELETE PATTERN error for '{pattern}': {e}")
            return 0
        except Exception as e:
            logger.warning(f"Redis DELETE PATTERN error for '{pattern}': {e}")
            return 0

    async def close(self):
        """Close Redis connection gracefully."""
        # Set shutdown flag to prevent new operations
        self._shutting_down = True

        if self._client:
            try:
                # Check if event loop is still running before closing
                if self._is_event_loop_running():
                    await self._client.close()
                    logger.info("Redis connection closed")
                else:
                    logger.debug("Redis connection not closed: Event loop already stopped")
            except RuntimeError as e:
                if "Event loop is closed" in str(e):
                    logger.debug("Redis close skipped: Event loop already closed")
                else:
                    logger.warning(f"Error closing Redis connection: {e}")
            except Exception as e:
                logger.warning(f"Error closing Redis connection: {e}")
            finally:
                self._client = None
                self._connected = False
