import os
import logging
from logging.handlers import RotatingFileHandler

def mkdirs(directory: str) -> str:
    """Create the directory if it does not exist and return its path."""
    if not os.path.exists(directory):
        os.makedirs(directory)
    return directory

class AbstractLogManager():
    """
    A singleton log manager that configures and returns loggers
    with rotating file handlers and an optional console handler.
    """
    def __init__(self, name: str = "AbstractLogManager"):
        self.logger = logging.getLogger(name)
        # Set logger's level to DEBUG so that all messages pass to handlers.
        self.logger.setLevel(logging.DEBUG)
        # We'll store our handlers here.
        self.handlers = {}

    def clear_handlers(self) -> None:
        """Remove all existing handlers from the logger."""
        for handler in self.logger.handlers[:]:
            self.logger.removeHandler(handler)
        self.handlers.clear()

    def add_file_handler(self, bpName: str, maxBytes: int, backupCount: int) -> None:
        """Add a rotating file handler to the logger."""
        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)
        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: int) -> None:
        """Add a console (stream) handler with the given level."""
        console_handler = logging.StreamHandler()
        console_handler.setLevel(level)
        formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
        console_handler.setFormatter(formatter)
        self.logger.addHandler(console_handler)
        self.handlers["console"] = console_handler

    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.
    
    Parameters:
      bpName (str): Base name for the logger (also used for the log filename).
      maxBytes (int): Maximum bytes before the log file rotates.
      backupCount (int): Number of rotated files to keep.
      console (bool): Whether to add console logging.
      debug (bool): If True, console logging will be set to DEBUG level.
      info (bool): If True (and debug is False), console logging is set to INFO.
      warning (bool): If True (and debug and info are False), console logging is set to WARNING.
      error (bool): If True, console logging will include ERROR messages.
      critical (bool): If True, console logging will include CRITICAL messages.
      
    Returns:
      logging.Logger: The configured logger.
    """
    # Instantiate (or retrieve) the log manager singleton for this logger name.
    log_mgr = AbstractLogManager(bpName)
    
    # Clear any existing handlers so that the configuration is fresh.
    log_mgr.clear_handlers()
    
    # Always add the file handler.
    log_mgr.add_file_handler(bpName, maxBytes, backupCount)
    
    # Determine the console log level based on booleans.
    if console:
        if debug:
            console_level = logging.DEBUG
        elif info:
            console_level = logging.INFO
        elif warning:
            console_level = logging.WARNING
        else:
            console_level = logging.ERROR  # Default fallback
        log_mgr.add_console_handler(console_level)
    
    return log_mgr.get_logger()
