import os
import logging
from logging.handlers import RotatingFileHandler
from abstract_classes import SingletonMeta  # Your singleton metaclass
from path_utils import mkdirs  # Helper that creates a directory if it doesn't exist

# Custom filter that allows or blocks records based on level booleans.
class LevelFilter(logging.Filter):
    def __init__(self, debug: bool, info: bool, warning: bool, error: bool, critical: bool):
        super().__init__()
        self.debug = debug
        self.info = info
        self.warning = warning
        self.error = error
        self.critical = critical

    def filter(self, record: logging.LogRecord) -> bool:
        if record.levelno == logging.DEBUG:
            return self.debug
        elif record.levelno == logging.INFO:
            return self.info
        elif record.levelno == logging.WARNING:
            return self.warning
        elif record.levelno == logging.ERROR:
            return self.error
        elif record.levelno == logging.CRITICAL:
            return self.critical
        return False

class AbstractLogManager(metaclass=SingletonMeta):
    """
    A singleton log manager that configures a logger with a rotating file handler
    and an optional console handler that uses a custom level filter.
    """
    def __init__(self, name: str = "AbstractLogManager"):
        self.logger = logging.getLogger(name)
        # Set the logger level to DEBUG so all records are processed; filtering is done by handlers.
        self.logger.setLevel(logging.DEBUG)
        self.handlers = {}

    def add_file_handler(self, bpName: str, maxBytes: int, backupCount: int) -> None:
        """Add a rotating file handler if not already added."""
        if "file" not in self.handlers:
            log_dir = mkdirs("logs")
            log_path = os.path.join(log_dir, f"{bpName}.log")
            file_handler = RotatingFileHandler(log_path, maxBytes=maxBytes, backupCount=backupCount)
            file_handler.setLevel(logging.INFO)  # File always gets INFO and above
            formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
            file_handler.setFormatter(formatter)
            self.logger.addHandler(file_handler)
            self.handlers["file"] = file_handler

    def add_console_handler(self, level_filter: LevelFilter) -> None:
        """Add a console handler with the given level filter if not already added."""
        if "console" not in self.handlers:
            console_handler = logging.StreamHandler()
            # Set the handler level low so that our custom filter controls what passes.
            console_handler.setLevel(logging.DEBUG)
            formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
            console_handler.setFormatter(formatter)
            console_handler.addFilter(level_filter)
            self.logger.addHandler(console_handler)
            self.handlers["console"] = console_handler
        else:
            # Update the filter on the existing console handler.
            self.handlers["console"].filters.clear()
            self.handlers["console"].addFilter(level_filter)

    def get_logger(self) -> logging.Logger:
        """Return the configured logger."""
        return self.logger

def get_logFile(
    bpName: str,
    maxBytes: int = 100000,
    backupCount: int = 3,
    console: bool = True,
    debug: bool = True,
    info: bool = False,
    warning: bool = False,
    error: bool = True,
    critical: bool = True
) -> logging.Logger:
    """
    Returns a logger with a rotating file handler and optional console handler,
    where the console handler is filtered by the provided booleans.

    Parameters:
      bpName (str): Base name for the logger (and used for the log filename).
      maxBytes (int): Maximum bytes before the log file rotates.
      backupCount (int): Number of backup files to keep.
      console (bool): Whether to add console logging.
      debug (bool): If True, include DEBUG messages on console.
      info (bool): If True, include INFO messages on console.
      warning (bool): If True, include WARNING messages on console.
      error (bool): If True, include ERROR messages on console.
      critical (bool): If True, include CRITICAL messages on console.
    
    Returns:
      logging.Logger: The configured logger.
    """
    # Instantiate the log manager (singleton) using bpName as the logger name.
    log_mgr = AbstractLogManager(bpName)
    
    # Always add the file handler.
    log_mgr.add_file_handler(bpName, maxBytes, backupCount)
    
    # If console logging is enabled, add a console handler with a custom filter.
    if console:
        level_filter = LevelFilter(debug=debug, info=info, warning=warning, error=error, critical=critical)
        log_mgr.add_console_handler(level_filter)
    
    return log_mgr.get_logger()

