"""
Logging configuration for CAVORA.
Sets up structured logging for debugging and monitoring.

Author: Samir Baladi
"""

import logging
import sys
import os
from datetime import datetime
from typing import Optional
import json


def setup_logger(name: str = "cavora",
                log_level: str = "INFO",
                log_file: Optional[str] = None,
                console_output: bool = True,
                json_format: bool = False) -> logging.Logger:
    """
    Set up a logger with consistent formatting.
    
    Args:
        name: Logger name
        log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
        log_file: Optional file path for logging
        console_output: Whether to output to console
        json_format: Whether to use JSON format for logs
        
    Returns:
        Configured logger
    """
    # Create logger
    logger = logging.getLogger(name)
    logger.setLevel(getattr(logging, log_level.upper()))
    
    # Remove existing handlers
    logger.handlers.clear()
    
    # Create formatters
    if json_format:
        formatter = JsonFormatter()
    else:
        formatter = logging.Formatter(
            '%(asctime)s | %(name)s | %(levelname)s | %(filename)s:%(lineno)d | %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
    
    # Add console handler
    if console_output:
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)
    
    # Add file handler
    if log_file:
        # Create log directory if it doesn't exist
        log_dir = os.path.dirname(log_file)
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir)
        
        file_handler = logging.FileHandler(log_file, encoding='utf-8')
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)
    
    return logger


def get_logger(name: str = "cavora") -> logging.Logger:
    """
    Get an existing logger or create a new one with default settings.
    
    Args:
        name: Logger name
        
    Returns:
        Logger instance
    """
    logger = logging.getLogger(name)
    
    # If logger has no handlers, set up default
    if not logger.handlers:
        logger = setup_logger(name)
    
    return logger


class JsonFormatter(logging.Formatter):
    """
    JSON formatter for structured logging.
    """
    
    def format(self, record: logging.LogRecord) -> str:
        """Format log record as JSON."""
        log_record = {
            'timestamp': datetime.fromtimestamp(record.created).isoformat(),
            'name': record.name,
            'level': record.levelname,
            'module': record.module,
            'line': record.lineno,
            'message': record.getMessage()
        }
        
        # Add exception info if present
        if record.exc_info:
            log_record['exception'] = self.formatException(record.exc_info)
        
        # Add extra attributes
        if hasattr(record, 'extra'):
            log_record['extra'] = record.extra
        
        return json.dumps(log_record)


class LoggerMixin:
    """
    Mixin class to add logging capability to any class.
    """
    
    @property
    def logger(self) -> logging.Logger:
        """Get logger for this class."""
        if not hasattr(self, '_logger'):
            self._logger = get_logger(self.__class__.__name__)
        return self._logger
    
    def log_debug(self, msg: str, *args, **kwargs):
        """Log debug message."""
        self.logger.debug(msg, *args, **kwargs)
    
    def log_info(self, msg: str, *args, **kwargs):
        """Log info message."""
        self.logger.info(msg, *args, **kwargs)
    
    def log_warning(self, msg: str, *args, **kwargs):
        """Log warning message."""
        self.logger.warning(msg, *args, **kwargs)
    
    def log_error(self, msg: str, *args, **kwargs):
        """Log error message."""
        self.logger.error(msg, *args, **kwargs)
    
    def log_critical(self, msg: str, *args, **kwargs):
        """Log critical message."""
        self.logger.critical(msg, *args, **kwargs)
    
    def log_exception(self, msg: str, *args, **kwargs):
        """Log exception with traceback."""
        self.logger.exception(msg, *args, **kwargs)


class PerformanceLogger:
    """
    Context manager for logging performance metrics.
    
    Example:
        with PerformanceLogger("data_processing"):
            # do something
    """
    
    def __init__(self, operation: str, logger: Optional[logging.Logger] = None):
        """
        Initialize performance logger.
        
        Args:
            operation: Name of operation being timed
            logger: Logger to use (uses default if None)
        """
        self.operation = operation
        self.logger = logger or get_logger()
        self.start_time = None
    
    def __enter__(self):
        """Start timing."""
        self.start_time = datetime.now()
        self.logger.info(f"Starting {self.operation}")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """End timing and log duration."""
        duration = (datetime.now() - self.start_time).total_seconds()
        
        if exc_type is None:
            self.logger.info(f"Completed {self.operation} in {duration:.3f}s")
        else:
            self.logger.error(f"Failed {self.operation} after {duration:.3f}s: {exc_val}")


class ProgressLogger:
    """
    Log progress for long-running operations.
    
    Example:
        progress = ProgressLogger(total=100, logger=logger)
        for i in range(100):
            # do something
            progress.update()
    """
    
    def __init__(self, 
                 total: int,
                 logger: Optional[logging.Logger] = None,
                 log_interval: int = 10):
        """
        Initialize progress logger.
        
        Args:
            total: Total number of items
            logger: Logger to use
            log_interval: Log every N items
        """
        self.total = total
        self.logger = logger or get_logger()
        self.log_interval = log_interval
        self.current = 0
        self.start_time = datetime.now()
    
    def update(self, n: int = 1):
        """Update progress by n items."""
        self.current += n
        
        if self.current % self.log_interval == 0 or self.current == self.total:
            pct = (self.current / self.total) * 100
            elapsed = (datetime.now() - self.start_time).total_seconds()
            
            if self.current > 0:
                rate = self.current / elapsed
                remaining = (self.total - self.current) / rate if rate > 0 else 0
                self.logger.info(
                    f"Progress: {self.current}/{self.total} ({pct:.1f}%) - "
                    f"Rate: {rate:.1f} items/s - ETA: {remaining:.1f}s"
                )
            else:
                self.logger.info(f"Progress: {self.current}/{self.total} ({pct:.1f}%)")
    
    def reset(self):
        """Reset progress counter."""
        self.current = 0
        self.start_time = datetime.now()


# Create default logger
default_logger = setup_logger()


# Example usage
if __name__ == "__main__":
    # Setup logger
    logger = setup_logger("example", log_level="DEBUG")
    
    print("="*60)
    print("CAVORA - Logger Example")
    print("="*60)
    
    # Basic logging
    logger.debug("This is a debug message")
    logger.info("This is an info message")
    logger.warning("This is a warning message")
    
    # Performance logging
    with PerformanceLogger("test_operation", logger):
        import time
        time.sleep(0.5)
    
    # Progress logging
    progress = ProgressLogger(total=50, logger=logger, log_interval=10)
    for i in range(50):
        time.sleep(0.01)
        progress.update()
    
    print("\n" + "="*60)
