"""Provide logger functionality."""

import inspect
import logging
from contextlib import AbstractContextManager
from functools import wraps
from pathlib import Path

from mhm_tools.common.constants import LOG_LEVEL_STR, LOG_LEVELS


def configure_mhm_tools_logger(
    log_level=None,
    count_verbose=0,
    count_quiet=0,
    log_file=None,
    log_file_level=None,
    no_colsole_logging=False,
):
    """Configure the parser setting formating as well as Stream and Filehandler."""
    logger = logging.getLogger("mhm_tools")
    logger.propagate = False
    general_level, error_msg_gnrl, error_msg_gnrl2 = get_lowest_level(
        log_level=log_level,
        log_file_level=log_file_level,
        count_verbose=count_verbose,
        count_quiet=count_quiet,
    )
    logger.setLevel(general_level)
    error_msg_fh, error_msg_ch = None, None
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    if not no_colsole_logging:
        ch = logging.StreamHandler()
        ch.setFormatter(formatter)
        ch_level, error_msg_ch = get_log_level(log_level, count_verbose, count_quiet)
        ch.setLevel(ch_level)
        logger.addHandler(ch)
        logger.info(f"Steam Handler set to log_level {LOG_LEVEL_STR[ch_level]}")
    if log_file:
        log_file = Path(log_file)
        if not log_file.suffix:
            logger.warning(f"Log file does not point to a file: {log_file}")
        else:
            if not log_file.parent.is_dir():
                logger.info(f"Creating folder for log_files {log_file.parent}")
                log_file.parent.mkdir(parents=True)
            if log_file.exists():
                logger.debug("Deleting existing log file.")
                log_file.unlink()
            fh = logging.FileHandler(log_file)
            fh.setFormatter(formatter)
            if log_file_level is not None:
                fh_level, error_msg_fh = get_log_level(
                    log_file_level, count_verbose, count_quiet
                )
            else:
                fh_level = general_level
            fh.setLevel(fh_level)
            logger.addHandler(fh)
            logger.info(f"File Handler set to log_level {LOG_LEVEL_STR[fh_level]}")
            logger.info(f"Writing logs to {log_file}")
    if error_msg_gnrl is not None:
        logger.error(f"Logger: {error_msg_gnrl}")
    if error_msg_ch is not None:
        logger.error(f"StreamHandler: {error_msg_ch}")
    if error_msg_fh is not None:
        logger.error(f"FileHandler: {error_msg_fh}")


def get_lowest_level(log_level, log_file_level, count_verbose, count_quiet):
    """Return the most verbose log level of all handlers."""
    llevel, ll_msg = get_log_level(
        log_level, count_verbose=count_verbose, count_quiet=count_quiet
    )
    if log_file_level is not None:
        lflevel, lf_msg = get_log_level(log_file_level)
    else:
        lflevel, lf_msg = llevel, None
    return min(llevel, lflevel), ll_msg, lf_msg


def get_log_level(level=None, count_verbose=0, count_quiet=0):
    """Set the logging level.

    Parameters
    ----------
    level : str
        logging level
    count_verbose : int
        verbosity
    count_quiet : int
        quietness

    returns level: int
    """
    error_msg = None
    if level is None:
        level = LOG_LEVELS["INFO"] - 10 * count_verbose + 10 * count_quiet
    elif not isinstance(level, int):
        if not isinstance(level, str):
            error_msg = (
                f"Invalid log level type: {type(level)} - using default log level INFO"
            )
            level = "INFO"
        level = level.upper()
        if level not in LOG_LEVELS:
            error_msg = f"Invalid log level: {level} - using default log level INFO"
            level = "INFO"
        level = LOG_LEVELS[level.upper()]
    return level, error_msg


def log_arguments(log_level='Debug'):
    """Log all non-None arguments passed to a function."""

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Get the signature of the function
            signature = inspect.signature(func)
            bound_args = signature.bind(*args, **kwargs)
            bound_args.apply_defaults()
            # Extract arguments and filter out None values
            non_none_args = {
                k: v for k, v in bound_args.arguments.items() if v is not None
            }

            # Log the arguments
            logger = logging.getLogger(inspect.getmodule(func).__name__)
            msg = f"Function '{func.__name__}' called with the following arguments: \n"
            for arg, value in non_none_args.items():
                msg += f"  {arg}: {value} \n"
            if log_level.upper() == "INFO":
                logger.info(msg)
            else:
                logger.debug(msg)
            # Call the original function
            try:
                return func(*args, **kwargs)
            except Exception as e:
                with ErrorLogger(logger):
                    raise e

        return wrapper

    return decorator


class ErrorLogger(AbstractContextManager):
    """
    Context manager to log Exceptions.

    Parameters
    ----------
    logger : string, None or logging.Logger instance, optional
        Logger name to use. Will be the root logger by default.
    do_log : Bool, optional
        Whether to really log errors. Will be true by default.
    """

    def __init__(self, logger=None, do_log=True):
        self.logger = logger.name if isinstance(logger, logging.Logger) else logger
        self.do_log = do_log

    def __exit__(self, exc_type, exc_value, traceback):
        """Log all exception messages."""
        if exc_value is not None and self.do_log:
            logging.getLogger(self.logger).exception(exc_value)

    def __enter__(self):
        return self