"""
This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences)
"""

import logging
import logging.config
import os

import yaml
import zmq
from zmq.log.handlers import PUBHandler

from control_backend.core.config import settings


def add_logging_level(level_name: str, level_num: int, method_name: str | None = None) -> None:
    """
    Adds a logging level to the `logging` module and the
    currently configured logging class.
    """
    if not method_name:
        method_name = level_name.lower()

    if hasattr(logging, level_name):
        raise AttributeError(f"{level_name} already defined in logging module")
    if hasattr(logging, method_name):
        raise AttributeError(f"{method_name} already defined in logging module")
    if hasattr(logging.getLoggerClass(), method_name):
        raise AttributeError(f"{method_name} already defined in logger class")

    def log_for_level(self, message, *args, **kwargs):
        if self.isEnabledFor(level_num):
            self._log(level_num, message, args, **kwargs)

    def log_to_root(message, *args, **kwargs):
        logging.log(level_num, message, *args, **kwargs)

    logging.addLevelName(level_num, level_name)
    setattr(logging, level_name, level_num)
    setattr(logging.getLoggerClass(), method_name, log_for_level)
    setattr(logging, method_name, log_to_root)


def setup_logging(path: str = settings.logging_settings.logging_config_file) -> None:
    """
    Setup logging configuration of the CB. Tries to load the logging configuration from a file,
    in which we specify custom loggers, formatters, handlers, etc.
    :param path:
    :return:
    """
    if os.path.exists(path):
        with open(path) as f:
            try:
                config = yaml.safe_load(f.read())
            except (AttributeError, yaml.YAMLError) as e:
                logging.warning(f"Could not load logging configuration: {e}")
                config = {}

            custom_levels = config.get("custom_levels", {}) or {}
            for level_name, level_num in custom_levels.items():
                add_logging_level(level_name, level_num)

            if config.get("handlers") is not None and config.get("handlers").get("ui"):
                pub_socket = zmq.Context.instance().socket(zmq.PUB)
                pub_socket.connect(settings.zmq_settings.internal_pub_address)
                config["handlers"]["ui"]["interface_or_socket"] = pub_socket

            logging.config.dictConfig(config)

            # Patch ZMQ PUBHandler to know about custom levels
            if custom_levels:
                for logger_name in config.get("loggers", {}):
                    logger = logging.getLogger(logger_name)
                    for handler in logger.handlers:
                        if isinstance(handler, PUBHandler):
                            # Use the INFO formatter as the default template
                            default_fmt = handler.formatters[logging.INFO]
                            for level_num in custom_levels.values():
                                handler.setFormatter(default_fmt, level=level_num)

    else:
        logging.warning("Logging config file not found. Using default logging configuration.")
