"""
Redis log handler for SAMSON PACS system.

This module provides Redis-based logging handler that:
1. Stores logs in Redis with TTL for automatic cleanup
2. Publishes logs to Redis Pub/Sub channel for real-time streaming
3. Supports log history with configurable retention
4. Provides structured logging with metadata
"""

import redis
import json
import logging
import os
import datetime
from typing import Optional, Dict, Any
import threading

class RedisLogHandler(logging.Handler):
    """
    Redis-based logging handler.
    
    Stores logs in Redis with the following structure:
    - List: 'pacs:logs' - recent logs (FIFO, limited by max_history)
    - Pub/Sub: 'pacs:logs:stream' - real-time log streaming
    - Sorted Set: 'pacs:logs:by_date:{date}' - logs indexed by date for queries
    """
    
    def __init__(
        self,
        host: str = 'localhost',
        port: int = 6379,
        db: int = 0,
        max_history: int = 10000,
        ttl_days: int = 7,
        enable_console_fallback: bool = True
    ):
        """
        Initialize Redis log handler.
        
        Args:
            host: Redis host
            port: Redis port
            db: Redis database number
            max_history: Maximum number of logs to keep in history
            ttl_days: Time-to-live for logs in days
            enable_console_fallback: Fallback to console if Redis is unavailable
        """
        super().__init__()
        
        self.host = host
        self.port = port
        self.db = db
        self.max_history = max_history
        self.ttl_seconds = ttl_days * 86400  # Convert days to seconds
        self.enable_console_fallback = enable_console_fallback
        
        # Redis keys
        self.logs_key = "pacs:logs"
        self.stream_channel = "pacs:logs:stream"
        self.stats_key = "pacs:logs:stats"
        
        # Thread lock for thread-safe operations
        self._lock = threading.RLock()
        
        # Redis connection (lazy initialization)
        self._redis: Optional[redis.Redis] = None
        self._redis_available = False
        
        # Console fallback handler
        self._console_handler: Optional[logging.Handler] = None
        if enable_console_fallback:
            self._console_handler = logging.StreamHandler()
            self._console_handler.setFormatter(logging.Formatter(
                '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            ))
        
        # Initialize connection
        self._init_redis()
    
    def _init_redis(self) -> bool:
        """Initialize Redis connection."""
        try:
            self._redis = redis.Redis(
                host=self.host,
                port=self.port,
                db=self.db,
                decode_responses=True,
                socket_connect_timeout=5,
                socket_timeout=5,
                retry_on_timeout=True
            )
            
            # Test connection
            self._redis.ping()
            self._redis_available = True
            
            # Configure Redis memory policy if we have control
            try:
                # Try to set memory policy (may fail without admin rights)
                self._redis.config_set('maxmemory', '100mb')
                self._redis.config_set('maxmemory-policy', 'allkeys-lru')
            except redis.ResponseError:
                # Ignore if we can't set config (normal in restricted environments)
                pass
                
            return True
            
        except (redis.ConnectionError, redis.TimeoutError) as e:
            self._redis_available = False
            if self.enable_console_fallback:
                print(f"Redis connection failed ({self.host}:{self.port}): {e}")
                print("Falling back to console logging")
            return False
    
    def _ensure_redis(self) -> bool:
        """Ensure Redis connection is available."""
        if self._redis_available and self._redis:
            try:
                self._redis.ping()
                return True
            except (redis.ConnectionError, redis.TimeoutError):
                self._redis_available = False
        
        # Try to reconnect
        return self._init_redis()
    
    def _format_log_entry(self, record: logging.LogRecord) -> Dict[str, Any]:
        """Format log record as structured dictionary."""
        return {
            'id': f"{int(record.created * 1000)}:{record.thread}:{record.lineno}",
            'timestamp': datetime.datetime.fromtimestamp(record.created).isoformat(),
            'timestamp_epoch': record.created,
            'level': record.levelname,
            'levelno': record.levelno,
            'message': record.getMessage(),
            'module': record.module,
            'logger': record.name,
            'funcName': record.funcName,
            'lineno': record.lineno,
            'thread': record.threadName,
            'process': record.process,
            'processName': record.processName,
            'pathname': record.pathname,
            'filename': record.filename,
        }
    
    def emit(self, record: logging.LogRecord):
        """
        Emit a log record to Redis.
        
        This method is called by the logging framework for each log record.
        """
        with self._lock:
            # Format the log entry
            log_entry = self._format_log_entry(record)
            json_log = json.dumps(log_entry, ensure_ascii=False)
            

            
            # Try to send to Redis
            redis_success = False
            if self._ensure_redis():
                try:
                    # Start Redis pipeline for atomic operations
                    pipe = self._redis.pipeline()
                    
                    # 1. Store in main logs list (FIFO)
                    pipe.lpush(self.logs_key, json_log)
                    pipe.ltrim(self.logs_key, 0, self.max_history - 1)
                    pipe.expire(self.logs_key, self.ttl_seconds)
                    
                    # 2. Publish to stream channel for real-time
                    pipe.publish(self.stream_channel, json_log)

                    
                    # 3. Store in date-based sorted set for queries
                    date_str = log_entry['timestamp'][:10]  # YYYY-MM-DD
                    date_key = f"pacs:logs:by_date:{date_str}"
                    score = int(record.created * 1000)  # Milliseconds for sorting
                    pipe.zadd(date_key, {json_log: score})
                    pipe.zremrangebyrank(date_key, 0, -self.max_history - 1)  # Keep only N
                    pipe.expire(date_key, self.ttl_seconds)
                    
                    # 4. Update statistics
                    level_key = f"pacs:logs:stats:{log_entry['level'].lower()}"
                    pipe.incr(level_key)
                    pipe.expire(level_key, 86400)  # 24 hours for stats
                    
                    # Execute all commands
                    pipe.execute()
                    
                    redis_success = True
                    
                except (redis.ConnectionError, redis.TimeoutError, redis.ResponseError) as e:
                    self._redis_available = False
                    if self.enable_console_fallback:
                        print(f"Redis error during log emit: {e}")
            
            # Fallback to console if Redis failed
            if not redis_success and self.enable_console_fallback and self._console_handler:
                try:

                    self._console_handler.emit(record)
                except:
                    self.handleError(record)
    
    def close(self):
        """Clean up resources."""
        with self._lock:
            if self._redis:
                try:
                    self._redis.close()
                except:
                    pass
                self._redis = None
            
            if self._console_handler:
                self._console_handler.close()
            
            super().close()
    
    def get_stats(self) -> Dict[str, Any]:
        """Get logging statistics from Redis."""
        if not self._ensure_redis():
            return {}
        
        try:
            stats = {}
            levels = ['debug', 'info', 'warning', 'error', 'critical']
            
            for level in levels:
                key = f"pacs:logs:stats:{level}"
                count = self._redis.get(key)
                if count:
                    stats[level] = int(count)
            
            # Get total logs count
            stats['total_logs'] = self._redis.llen(self.logs_key)
            
            return stats
            
        except (redis.ConnectionError, redis.TimeoutError):
            return {}
    
    def get_recent_logs(self, limit: int = 100, level: Optional[str] = None) -> list:
        """Get recent logs from Redis."""
        if not self._ensure_redis():
            return []
        
        try:
            # Get logs from Redis list
            logs_json = self._redis.lrange(self.logs_key, 0, limit - 1)
            logs = []
            
            for json_log in logs_json:
                try:
                    log_entry = json.loads(json_log)
                    
                    # Apply level filter if specified
                    if level and log_entry['level'].lower() != level.lower():
                        continue
                    
                    logs.append(log_entry)
                except json.JSONDecodeError:
                    continue
            
            return logs
            
        except (redis.ConnectionError, redis.TimeoutError):
            return []


def setup_redis_logging(
    logger_name: str = 'sampacs',
    host: Optional[str] = None,
    port: Optional[int] = None,
    **kwargs
) -> Optional[RedisLogHandler]:
    """
    Setup Redis logging for a logger.
    
    Args:
        logger_name: Name of the logger to configure
        host: Redis host (default: REDIS_HOST env var or 'localhost')
        port: Redis port (default: REDIS_PORT env var or 6379)
        **kwargs: Additional arguments for RedisLogHandler
    
    Returns:
        RedisLogHandler instance if successful, None otherwise
    """
    # Get Redis configuration from environment or defaults
    redis_host = host or os.environ.get('REDIS_HOST', 'localhost')
    redis_port = port or int(os.environ.get('REDIS_PORT', 6379))
    
    # Check if Redis logging is enabled
    if os.environ.get('REDIS_LOGGING', 'true').lower() == 'false':
        return None
    
    try:
        # Create Redis log handler
        redis_handler = RedisLogHandler(
            host=redis_host,
            port=redis_port,
            **kwargs
        )
        
        # Configure formatter
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(message)s'
        )
        redis_handler.setFormatter(formatter)
        
        # Get or create logger
        logger = logging.getLogger(logger_name)
        
        # Remove existing handlers if Redis is available
        if redis_handler._redis_available:
            # Remove ALL existing handlers to avoid duplicates
            for handler in logger.handlers[:]:
                logger.removeHandler(handler)
        
        # Add Redis handler
        logger.addHandler(redis_handler)
        
        # Set log level from environment or default
        log_level = os.environ.get('LOG_LEVEL', 'INFO').upper()
        logger.setLevel(getattr(logging, log_level, logging.INFO))
        
        # Don't propagate to root logger to avoid duplicate logs
        logger.propagate = False
        
        return redis_handler
        
    except Exception as e:
        print(f"Failed to setup Redis logging: {e}")
        return None


# Global Redis handler instance for easy access
_redis_handler: Optional[RedisLogHandler] = None

def get_redis_handler() -> Optional[RedisLogHandler]:
    """Get the global Redis log handler instance."""
    return _redis_handler

def init_redis_logging(**kwargs) -> Optional[RedisLogHandler]:
    """
    Initialize Redis logging globally.
    
    Returns:
        RedisLogHandler instance if successful
    """
    global _redis_handler
    _redis_handler = setup_redis_logging(**kwargs)
    return _redis_handler