"""
Structured logging system for CutySoup with enterprise features
"""
import json
import logging
import logging.handlers
import time
import threading
from typing import Dict, Any, Optional, Callable
from enum import Enum
from dataclasses import dataclass, asdict
from pathlib import Path
import sys


class LogLevel(Enum):
    """Log levels for CutySoup."""
    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


class LogFormat(Enum):
    """Log output formats."""
    TEXT = "text"
    JSON = "json"
    STRUCTURED = "structured"


@dataclass
class LogContext:
    """Context information for log entries."""
    operation: Optional[str] = None
    document_size: Optional[int] = None
    tag_count: Optional[int] = None
    parsing_time: Optional[float] = None
    memory_usage: Optional[int] = None
    thread_id: Optional[str] = None
    request_id: Optional[str] = None
    user_id: Optional[str] = None
    session_id: Optional[str] = None
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary, excluding None values."""
        return {k: v for k, v in asdict(self).items() if v is not None}


class StructuredLogFormatter(logging.Formatter):
    """Custom formatter for structured logging."""
    
    def __init__(self, format_type: LogFormat = LogFormat.JSON, include_extra: bool = True):
        super().__init__()
        self.format_type = format_type
        self.include_extra = include_extra
    
    def format(self, record: logging.LogRecord) -> str:
        """Format log record as structured data."""
        log_data = {
            "timestamp": time.time(),
            "datetime": self.formatTime(record),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno,
            "thread": threading.current_thread().name,
            "thread_id": threading.get_ident()
        }
        
        # Add exception information if present
        if record.exc_info:
            log_data["exception"] = {
                "type": record.exc_info[0].__name__,
                "message": str(record.exc_info[1]),
                "traceback": self.formatException(record.exc_info)
            }
        
        # Add extra context information
        if self.include_extra:
            extra_data = {}
            for key, value in record.__dict__.items():
                if key not in ['name', 'msg', 'args', 'levelname', 'levelno', 'pathname',
                              'filename', 'module', 'exc_info', 'exc_text', 'stack_info',
                              'lineno', 'funcName', 'created', 'msecs', 'relativeCreated',
                              'thread', 'threadName', 'processName', 'process', 'message']:
                    extra_data[key] = value
            
            if extra_data:
                log_data["context"] = extra_data
        
        # Format based on type
        if self.format_type == LogFormat.JSON:
            return json.dumps(log_data, default=str, ensure_ascii=False)
        elif self.format_type == LogFormat.STRUCTURED:
            return self._format_structured(log_data)
        else:  # TEXT
            return self._format_text(log_data)
    
    def _format_structured(self, log_data: Dict[str, Any]) -> str:
        """Format as human-readable structured text."""
        parts = [
            f"[{log_data['datetime']}]",
            f"[{log_data['level']}]",
            f"[{log_data['logger']}]",
            log_data['message']
        ]
        
        if 'context' in log_data and log_data['context']:
            context_str = " ".join(f"{k}={v}" for k, v in log_data['context'].items())
            parts.append(f"({context_str})")
        
        if 'exception' in log_data:
            parts.append(f"Exception: {log_data['exception']['type']}: {log_data['exception']['message']}")
        
        return " ".join(parts)
    
    def _format_text(self, log_data: Dict[str, Any]) -> str:
        """Format as simple text."""
        message = f"[{log_data['level']}] {log_data['message']}"
        
        if 'exception' in log_data:
            message += f" | {log_data['exception']['type']}: {log_data['exception']['message']}"
        
        return message


class CutySoupLogger:
    """Advanced logging system for CutySoup."""
    
    def __init__(self, name: str = "cutysoup", level: LogLevel = LogLevel.INFO,
                 format_type: LogFormat = LogFormat.TEXT, 
                 log_file: Optional[str] = None,
                 max_file_size: int = 10 * 1024 * 1024,  # 10MB
                 backup_count: int = 5,
                 enable_console: bool = True):
        """
        Initialize CutySoup logger.
        
        Args:
            name: Logger name
            level: Logging level
            format_type: Output format
            log_file: Optional log file path
            max_file_size: Maximum log file size before rotation
            backup_count: Number of backup files to keep
            enable_console: Whether to log to console
        """
        self.name = name
        self.level = level
        self.format_type = format_type
        self.log_file = log_file
        self._logger = logging.getLogger(name)
        self._logger.setLevel(getattr(logging, level.value))
        
        # Clear existing handlers
        self._logger.handlers.clear()
        
        # Setup formatter
        self._formatter = StructuredLogFormatter(format_type)
        
        # Setup console handler
        if enable_console:
            console_handler = logging.StreamHandler(sys.stdout)
            console_handler.setFormatter(self._formatter)
            self._logger.addHandler(console_handler)
        
        # Setup file handler with rotation
        if log_file:
            log_path = Path(log_file)
            log_path.parent.mkdir(parents=True, exist_ok=True)
            
            file_handler = logging.handlers.RotatingFileHandler(
                log_file, maxBytes=max_file_size, backupCount=backup_count
            )
            file_handler.setFormatter(self._formatter)
            self._logger.addHandler(file_handler)
        
        # Thread-local storage for context
        self._local = threading.local()
    
    def set_context(self, **kwargs) -> None:
        """Set context for current thread."""
        if not hasattr(self._local, 'context'):
            self._local.context = {}
        self._local.context.update(kwargs)
    
    def clear_context(self) -> None:
        """Clear context for current thread."""
        if hasattr(self._local, 'context'):
            self._local.context.clear()
    
    def get_context(self) -> Dict[str, Any]:
        """Get current thread context."""
        return getattr(self._local, 'context', {})
    
    def _log_with_context(self, level: str, message: str, 
                         context: Optional[LogContext] = None, **kwargs) -> None:
        """Log message with context information."""
        # Merge thread context with provided context
        log_context = self.get_context().copy()
        
        if context:
            log_context.update(context.to_dict())
        
        # Add any additional kwargs
        log_context.update(kwargs)
        
        # Log with extra context
        getattr(self._logger, level.lower())(message, extra=log_context)
    
    def debug(self, message: str, context: Optional[LogContext] = None, **kwargs) -> None:
        """Log debug message."""
        self._log_with_context("DEBUG", message, context, **kwargs)
    
    def info(self, message: str, context: Optional[LogContext] = None, **kwargs) -> None:
        """Log info message."""
        self._log_with_context("INFO", message, context, **kwargs)
    
    def warning(self, message: str, context: Optional[LogContext] = None, **kwargs) -> None:
        """Log warning message."""
        self._log_with_context("WARNING", message, context, **kwargs)
    
    def error(self, message: str, context: Optional[LogContext] = None, 
              exc_info: bool = False, **kwargs) -> None:
        """Log error message."""
        if exc_info:
            kwargs['exc_info'] = True
        self._log_with_context("ERROR", message, context, **kwargs)
    
    def critical(self, message: str, context: Optional[LogContext] = None, 
                exc_info: bool = False, **kwargs) -> None:
        """Log critical message."""
        if exc_info:
            kwargs['exc_info'] = True
        self._log_with_context("CRITICAL", message, context, **kwargs)
    
    def exception(self, message: str, context: Optional[LogContext] = None, **kwargs) -> None:
        """Log exception with traceback."""
        kwargs['exc_info'] = True
        self._log_with_context("ERROR", message, context, **kwargs)
    
    def log_operation(self, operation: str, start_time: float, 
                     success: bool = True, **kwargs) -> None:
        """Log operation completion with timing."""
        duration = time.time() - start_time
        context = LogContext(
            operation=operation,
            parsing_time=duration,
            **kwargs
        )
        
        if success:
            self.info(f"Operation '{operation}' completed successfully", context)
        else:
            self.error(f"Operation '{operation}' failed", context)
    
    def log_performance(self, operation: str, metrics: Dict[str, Any]) -> None:
        """Log performance metrics."""
        context = LogContext(operation=operation, **metrics)
        self.info(f"Performance metrics for '{operation}'", context)
    
    def log_memory_usage(self, operation: str, memory_before: int, 
                        memory_after: int) -> None:
        """Log memory usage information."""
        memory_delta = memory_after - memory_before
        context = LogContext(
            operation=operation,
            memory_usage=memory_after,
            memory_delta=memory_delta
        )
        self.debug(f"Memory usage for '{operation}'", context)
    
    def set_level(self, level: LogLevel) -> None:
        """Change logging level."""
        self.level = level
        self._logger.setLevel(getattr(logging, level.value))
    
    def add_filter(self, filter_func: Callable[[logging.LogRecord], bool]) -> None:
        """Add custom filter to logger."""
        self._logger.addFilter(filter_func)
    
    def get_stats(self) -> Dict[str, Any]:
        """Get logging statistics."""
        stats = {
            "name": self.name,
            "level": self.level.value,
            "format": self.format_type.value,
            "handlers": len(self._logger.handlers),
            "filters": len(self._logger.filters),
            "log_file": self.log_file
        }
        
        # Add file handler stats if available
        for handler in self._logger.handlers:
            if isinstance(handler, logging.handlers.RotatingFileHandler):
                try:
                    file_size = Path(handler.baseFilename).stat().st_size
                    stats["file_size_bytes"] = file_size
                    stats["max_file_size_bytes"] = handler.maxBytes
                    stats["backup_count"] = handler.backupCount
                except Exception:
                    pass
        
        return stats


class LogManager:
    """Global log manager for CutySoup."""
    
    def __init__(self):
        self._loggers: Dict[str, CutySoupLogger] = {}
        self._default_config = {
            "level": LogLevel.INFO,
            "format_type": LogFormat.TEXT,
            "enable_console": True
        }
    
    def get_logger(self, name: str = "cutysoup", **kwargs) -> CutySoupLogger:
        """Get or create logger with specified configuration."""
        if name not in self._loggers:
            config = self._default_config.copy()
            config.update(kwargs)
            self._loggers[name] = CutySoupLogger(name, **config)
        
        return self._loggers[name]
    
    def configure_default(self, **kwargs) -> None:
        """Configure default settings for new loggers."""
        self._default_config.update(kwargs)
    
    def set_global_level(self, level: LogLevel) -> None:
        """Set logging level for all loggers."""
        self._default_config["level"] = level
        for logger in self._loggers.values():
            logger.set_level(level)
    
    def enable_debug_mode(self) -> None:
        """Enable debug mode for all loggers."""
        self.set_global_level(LogLevel.DEBUG)
    
    def disable_debug_mode(self) -> None:
        """Disable debug mode (set to INFO level)."""
        self.set_global_level(LogLevel.INFO)
    
    def setup_enterprise_logging(self, log_dir: str, 
                                application_name: str = "cutysoup") -> None:
        """Setup enterprise-grade logging configuration."""
        log_path = Path(log_dir)
        log_path.mkdir(parents=True, exist_ok=True)
        
        # Main application log
        main_log = log_path / f"{application_name}.log"
        
        # Error log
        error_log = log_path / f"{application_name}-error.log"
        
        # Performance log
        perf_log = log_path / f"{application_name}-performance.log"
        
        # Configure main logger
        main_logger = self.get_logger(
            "cutysoup",
            level=LogLevel.INFO,
            format_type=LogFormat.JSON,
            log_file=str(main_log),
            max_file_size=50 * 1024 * 1024,  # 50MB
            backup_count=10
        )
        
        # Configure error logger
        error_logger = self.get_logger(
            "cutysoup.error",
            level=LogLevel.ERROR,
            format_type=LogFormat.JSON,
            log_file=str(error_log),
            enable_console=False
        )
        
        # Configure performance logger
        perf_logger = self.get_logger(
            "cutysoup.performance",
            level=LogLevel.DEBUG,
            format_type=LogFormat.JSON,
            log_file=str(perf_log),
            enable_console=False
        )
        
        return {
            "main": main_logger,
            "error": error_logger,
            "performance": perf_logger
        }
    
    def get_all_stats(self) -> Dict[str, Any]:
        """Get statistics for all loggers."""
        return {name: logger.get_stats() for name, logger in self._loggers.items()}
    
    def shutdown(self) -> None:
        """Shutdown all loggers and cleanup."""
        for logger in self._loggers.values():
            for handler in logger._logger.handlers[:]:
                handler.close()
                logger._logger.removeHandler(handler)
        
        self._loggers.clear()


# Global log manager instance
_log_manager = LogManager()


def get_logger(name: str = "cutysoup", **kwargs) -> CutySoupLogger:
    """Get logger instance from global manager."""
    return _log_manager.get_logger(name, **kwargs)


def configure_logging(**kwargs) -> None:
    """Configure default logging settings."""
    _log_manager.configure_default(**kwargs)


def enable_debug_logging() -> None:
    """Enable debug logging globally."""
    _log_manager.enable_debug_mode()


def disable_debug_logging() -> None:
    """Disable debug logging globally."""
    _log_manager.disable_debug_mode()


def setup_enterprise_logging(log_dir: str, application_name: str = "cutysoup") -> Dict[str, CutySoupLogger]:
    """Setup enterprise logging configuration."""
    return _log_manager.setup_enterprise_logging(log_dir, application_name)


def shutdown_logging() -> None:
    """Shutdown all logging."""
    _log_manager.shutdown()


# Context manager for logging operations
class LoggingContext:
    """Context manager for scoped logging context."""
    
    def __init__(self, logger: CutySoupLogger, **context):
        self.logger = logger
        self.context = context
        self.original_context = None
    
    def __enter__(self):
        self.original_context = self.logger.get_context().copy()
        self.logger.set_context(**self.context)
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.logger.clear_context()
        if self.original_context:
            self.logger.set_context(**self.original_context)


# Decorators for automatic logging
def logged_operation(logger_name: str = "cutysoup", log_args: bool = False):
    """Decorator to automatically log function operations."""
    def decorator(func):
        def wrapper(*args, **kwargs):
            logger = get_logger(logger_name)
            operation = f"{func.__module__}.{func.__name__}"
            
            start_time = time.time()
            
            # Log start
            context = LogContext(operation=operation)
            if log_args:
                context.args = str(args)[:200]  # Truncate for safety
                context.kwargs = str(kwargs)[:200]
            
            logger.debug(f"Starting operation '{operation}'", context)
            
            try:
                result = func(*args, **kwargs)
                logger.log_operation(operation, start_time, success=True)
                return result
            except Exception as e:
                logger.log_operation(operation, start_time, success=False, error=str(e))
                logger.exception(f"Operation '{operation}' failed")
                raise
        
        return wrapper
    return decorator