import datetime
import sys
import threading
from collections.abc import Callable
from typing import Final

from .letter_levels import LetterLevel


class Hermes:
    """
    Hermes is a lightweight, customizable, and thread-safe logging utility.

    It allows users to register specific consumer functions for different
    log severity levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL).
    Messages are formatted with a timestamp and a level prefix.

    Critical log levels (ERROR, FATAL) will automatically output to
    sys.stderr if no specific consumer is registered for them.
    """

    is_enabled: bool = True

    _consumers: dict[LetterLevel, Callable[[str], None] | None] = {
        LetterLevel.TRACE: None,
        LetterLevel.DEBUG: None,
        LetterLevel.INFO: None,
        LetterLevel.WARN: None,
        LetterLevel.ERROR: None,
        LetterLevel.FATAL: None,
    }

    _LETTERS_FORMAT: Final[dict[LetterLevel, str]] = {
        LetterLevel.TRACE: "TRC",
        LetterLevel.DEBUG: "DEB",
        LetterLevel.INFO: "INF",
        LetterLevel.WARN: "WAR",
        LetterLevel.ERROR: "ERR",
        LetterLevel.FATAL: "FAT",
    }

    time_format: str = "%Y-%m-%d %H:%M:%S"

    _consumer_lock: Final[threading.Lock] = threading.Lock()

    @classmethod
    def set_time_format(cls, time_format: str):
        cls.time_format = time_format

    @classmethod
    def set_consumer(cls, level: LetterLevel | str, consumer: Callable[[str], None] | None):
        """
        Sets the consumer function for a specific logging level.

        The consumer function will be called with the formatted log message
        (as a string) whenever a log message of the specified `level` is sent.
        Set to `None` to disable logging for that level.

        Args:
            level (LetterLevel | str): The logging level (e.g., LetterLevel.INFO or "INFO").
            consumer (Callable[[str], None] | None):
                The function to consume the log messages, or `None` to disable.
        """
        if isinstance(level, str):
            try:
                level = LetterLevel(level.upper())
            except ValueError as e:
                # If invalid string, we can either raise or log.
                # Since this is the logging utility, raising is safer to alert the dev.
                valid_levels = ", ".join([lvl.value for lvl in LetterLevel])
                raise ValueError(f"Invalid log level: '{level}'. Valid levels are: {valid_levels}") from e

        with cls._consumer_lock:
            cls._consumers[level] = consumer

    @classmethod
    def trace(cls, message: str, *args, **kwargs):
        """
        Sends a TRACE level log message.

        These messages are typically for fine-grained information about program flow,
        often used during development or deep debugging.

        Args:
            message (str): The base message string, which can contain format specifiers.
            *args: Positional arguments to format the message string.
            **kwargs: Keyword arguments to format the message string.
        """
        cls._send_letter(LetterLevel.TRACE, message, *args, **kwargs)

    @classmethod
    def debug(cls, message: str, *args, **kwargs):
        """
        Sends a DEBUG level log message.

        These messages provide detailed information, typically of interest
        only when diagnosing problems.

        Args:
            message (str): The base message string, which can contain format specifiers.
            *args: Positional arguments to format the message string.
            **kwargs: Keyword arguments to format the message string.
        """
        cls._send_letter(LetterLevel.DEBUG, message, *args, **kwargs)

    @classmethod
    def info(cls, message: str, *args, **kwargs):
        """
        Sends an INFO level log message.

        These messages confirm that things are working as expected.

        Args:
            message (str): The base message string, which can contain format specifiers.
            *args: Positional arguments to format the message string.
            **kwargs: Keyword arguments to format the message string.
        """
        cls._send_letter(LetterLevel.INFO, message, *args, **kwargs)

    @classmethod
    def warn(cls, message: str, *args, **kwargs):
        """
        Sends a WARN level log message.

        These messages indicate that something unexpected happened, or
        indicative of a problem that might not prevent the software from working.

        Args:
            message (str): The base message string, which can contain format specifiers.
            *args: Positional arguments to format the message string.
            **kwargs: Keyword arguments to format the message string.
        """
        cls._send_letter(LetterLevel.WARN, message, *args, **kwargs)

    @classmethod
    def warning(cls, message: str, *args, **kwargs):
        """
        Sends a WARN level log message.

        These messages indicate that something unexpected happened, or
        indicative of a problem that might not prevent the software from working.

        Args:
            message (str): The base message string, which can contain format specifiers.
            *args: Positional arguments to format the message string.
            **kwargs: Keyword arguments to format the message string.
        """
        cls._send_letter(LetterLevel.WARN, message, *args, **kwargs)

    @classmethod
    def error(cls, message: str, *args, **kwargs):
        """
        Sends an ERROR level log message.

        These messages indicate a more serious problem; the software has been
        unable to perform some function. If no consumer is set, messages at
        this level will be written to `sys.stderr`.

        Args:
            message (str): The base message string, which can contain format specifiers.
            *args: Positional arguments to format the message string.
            **kwargs: Keyword arguments to format the message string.
        """
        cls._send_letter(LetterLevel.ERROR, message, *args, **kwargs)

    @classmethod
    def fatal(cls, message: str, *args, **kwargs):
        """
        Sends a FATAL level log message.

        These messages indicate a severe error that likely causes the application
        to abort. If no consumer is set, messages at this level will be written
        to `sys.stderr`.

        Args:
            message (str): The base message string, which can contain format specifiers.
            *args: Positional arguments to format the message string.
            **kwargs: Keyword arguments to format the message string.
        """
        cls._send_letter(LetterLevel.FATAL, message, *args, **kwargs)

    @classmethod
    def _send_letter(cls, level: LetterLevel, message: str, *args, **kwargs):
        """
        Internal method to dispatch a log message to the appropriate consumer.

        It retrieves the consumer for the given level and, if available,
        formats the message and passes it to the consumer. If no specific
        consumer is set for ERROR or FATAL levels, it falls back to sys.stderr.

        Args:
            level (LetterLevel): The severity level of the log message.
            message (str): The unformatted message string.
            *args: Positional arguments for message formatting.
            **kwargs: Keyword arguments for message formatting.
        """

        if not cls.is_enabled:
            return

        with cls._consumer_lock:
            consumer = cls._consumers.get(level)

        # Format the message regardless, as it might go to sys.stderr
        formatted_message = cls._format_letter(level, message, *args, **kwargs) + "\n"

        if consumer is not None:
            consumer(formatted_message)
        elif level == LetterLevel.ERROR or level == LetterLevel.FATAL:
            # Fallback for ERROR and FATAL levels if no consumer is explicitly set
            sys.stderr.write(formatted_message)

    @classmethod
    def _format_letter(cls, letter_type: LetterLevel, message: str, *args, **kwargs) -> str:
        """
        Internal method to format a log message string.

        Prepends the current UTC datetime and the letter type's short format
        prefix to the provided message. The message itself can be formatted
        using `str.format()` with `*args` and `**kwargs`.

        Args:
            letter_type (LetterLevel): The severity level of the message.
            message (str): The raw message string, potentially containing format placeholders.
            *args: Positional arguments to pass to `message.format()`.
            **kwargs: Keyword arguments to pass to `message.format()`.

        Returns:
            str: The fully formatted log message ready for consumption.
        """
        formatted_message = message.format(*args, **kwargs) if args or kwargs else message
        level_prefix = cls._LETTERS_FORMAT[letter_type]
        return f"{datetime.datetime.now(datetime.timezone.utc).strftime(cls.time_format)} {level_prefix}: {formatted_message}"
