"""Logging class."""

from __future__ import annotations

import logging as lg
from enum import Enum
from pathlib import Path
from typing import Any, Optional

from vito_crop_classification.utils import get_current_time


class LogLevel(int, Enum):
    """Log levels."""

    DEBUG: int = 10
    INFO: int = 20
    TRACE: int = 25
    WARNING: int = 30
    ERROR: int = 40
    CRITICAL: int = 50

    def __str__(self) -> str:
        """String representation of the logging level."""
        return self.name


def add_logging_level(name: str, num: int, method: Optional[str] = None) -> None:
    """Comprehensively adds a new logging level to the `logging` module and the currently configured logging class."""
    if not method:
        method = name.lower()

    # Silently ignore if already created
    if hasattr(lg, name) or hasattr(lg, method) or hasattr(lg.getLoggerClass(), method):
        return

    def log_for_level(self: Any, message: str, *args: Any, **kwargs: Any) -> None:
        if self.isEnabledFor(num):
            self._log(num, message, args, **kwargs)

    def log_to_root(message: str, *args: Any, **kwargs: Any) -> None:
        lg.log(num, message, *args, **kwargs)

    lg.addLevelName(num, name)
    setattr(lg, name, num)
    setattr(lg.getLoggerClass(), method, log_for_level)
    setattr(lg, method, log_to_root)


# Add custom logging levels
add_logging_level("TRACE", LogLevel.TRACE)


class Logger:
    """Custom logger."""

    def __init__(self, lvl: LogLevel = LogLevel.TRACE, log_f: Path | None = None) -> None:
        """
        Initialise the logger.

        Parameters
        ----------
        lvl : int
            Logging level, TRACE (25) by default, options:
                - 10: DEBUG
                - 20: INFO
                - 25: TRACE
                - 30: WARNING
                - 40: ERROR
                - 50: CRITICAL
            Logging is done for lvl and above.
        log_f : Path | None
            Folder where logs are stored, not stored if None
        """
        self._lvl: int = LogLevel.TRACE
        self._path = log_f

        # Configure the logger
        self.set_level(lvl)

    def __call__(self, msg: str, lvl: LogLevel | int | None = None) -> None:
        """Perform logging."""
        lvl = LogLevel(lvl if lvl else self._lvl)
        msg = f"{get_current_time()} | {str(lvl):^8s} | {msg}"
        if self._path is not None:
            with open(self._path / "logs.txt", "a+") as f:
                f.write(msg + "\n")
        lg.log(level=self._lvl, msg=msg)

    def set_level(self, lvl: LogLevel) -> None:
        """Set a specific level."""
        lvl: int = LogLevel(lvl)
        if lvl <= LogLevel.DEBUG:
            lvl_tag = str(LogLevel.DEBUG)
        elif lvl <= LogLevel.INFO:
            lvl_tag = str(LogLevel.INFO)
        elif lvl <= LogLevel.TRACE:
            lvl_tag = str(LogLevel.TRACE)
        elif lvl <= LogLevel.WARNING:
            lvl_tag = str(LogLevel.WARNING)
        elif lvl <= LogLevel.ERROR:
            lvl_tag = str(LogLevel.ERROR)
        elif lvl <= LogLevel.CRITICAL:
            lvl_tag = str(LogLevel.CRITICAL)
        else:
            raise Exception(f"Level {lvl} doesn't exist!")
        self._lvl = lvl

        # Configure the logging
        lg.getLogger(__name__).setLevel(lvl_tag)
        lg.basicConfig(level=lvl)


# Create a black-hole logger (results never written to a file)
bh_logger = Logger()
