"""
ChitraGupt Logger Utility.
~~~~~~~~~~~~~~~~~~~~~~~~~

This module provides a robust logger implementation that supports colored logging,
log buffering, file logging, and various log levels. It is designed to meet fintech
industry standards and is fully type-safe. The logger uses colorama for cross-platform
color support.

Classes
-------
LogLevel : Enum
    Enumeration of available log levels.
LogType : Enum
    Enumeration of available log types.
LogColor : dataclass
    Color configuration for different log types.
LoggerConfig : dataclass
    Configuration class for logger settings.
LogBuffer : class
    Buffer class for storing log messages.
GuptLogger : class
    Enhanced logger with improved type safety and additional features.
"""

import dataclasses
import datetime
import enum
import os
import pathlib
import sys
import typing

import colorama
import msgspec

# Initialize colorama for cross-platform color support
colorama.init()


class LogLevel(
    str,
    enum.Enum,
):
    """
    Enumeration of available log levels with their corresponding logging types.

    Attributes
    ----------
    NO_ERROR : str
        No log messages are displayed.
    BASIC : str
        Only INFO and WARNING messages are logged.
    STANDARD : str
        INFO, WARNING, and ERROR messages are logged.
    DEBUG : str
        All types of log messages are logged.
    """

    NO_ERROR = "no-error"
    BASIC = "basic"
    STANDARD = "standard"
    DEBUG = "debug"


class LogType(
    str,
    enum.Enum,
):
    """
    Enumeration of available log types.

    Attributes
    ----------
    INFO : str
        Informational messages.
    WARNING : str
        Warning messages.
    DEBUG : str
        Debug messages.
    ERROR : str
        Error messages.
    """

    INFO = "info"
    WARNING = "warning"
    DEBUG = "debug"
    ERROR = "error"


@dataclasses.dataclass
class LogColor:
    """
    Color configuration for different log types.

    Attributes
    ----------
    INFO : str
        Color for informational messages.
    WARNING : str
        Color for warning messages.
    DEBUG : str
        Color for debug messages.
    ERROR : str
        Color for error messages.
    TIMESTAMP : str
        Color for timestamps.
    RESET : str
        Color reset code.
    """

    INFO: str = colorama.Fore.GREEN
    WARNING: str = colorama.Fore.YELLOW
    DEBUG: str = colorama.Fore.LIGHTBLACK_EX
    ERROR: str = colorama.Fore.RED
    TIMESTAMP: str = colorama.Fore.CYAN
    RESET: str = colorama.Fore.RESET


class LoggerConfig(msgspec.Struct):
    """
    Configuration class for logger settings.

    Parameters
    ----------
    name : str
        The name of the logger.
    level : LogLevel, optional
        The logging level (default is LogLevel.BASIC).
    log_file : str or pathlib.Path, optional
        Path to the log file (default is None).
    clean_logs : bool, optional
        If True, logs are written without extra formatting (default is True).
    enable_colors : bool, optional
        If True, colored output is enabled (default is True).
    timestamp_format : str, optional
        Format for timestamps (default is "%Y-%m-%d %H:%M:%S").
    buffer_size : int, optional
        Number of log messages to keep in memory (default is 1000).
    auto_flush : bool, optional
        If True, automatically flushes the buffer when full (default is True).
    """

    name: str
    level: LogLevel = LogLevel.BASIC
    log_file: typing.Optional[typing.Any] = None
    clean_logs: bool = True
    enable_colors: bool = True
    timestamp_format: str = "%Y-%m-%d %H:%M:%S"
    buffer_size: int = 1000
    auto_flush: bool = True


class LogBuffer:
    """
    Buffer class for storing log messages.

    Parameters
    ----------
    max_size : int, optional
        Maximum number of messages to keep in the buffer (default is 1000).

    Attributes
    ----------
    messages : List[str]
        List of buffered log messages.
    max_size : int
        Maximum buffer size.
    """

    def __init__(
        self,
        max_size: int = 1000,
    ) -> None:
        self.messages: typing.List[str] = []
        self.max_size = max_size

    def add(
        self,
        message: str,
    ) -> None:
        """
        Add a message to the buffer.

        Parameters
        ----------
        message : str
            The log message to add.
        """
        self.messages.append(message)
        if len(self.messages) > self.max_size:
            self.messages.pop(0)

    def flush(
        self,
    ) -> typing.List[str]:
        """
        Flush the buffer and return all messages.

        Returns
        -------
        List[str]
            A list of all flushed messages.
        """
        messages = self.messages.copy()
        self.messages.clear()
        return messages

    def get_all(
        self,
    ) -> typing.List[str]:
        """
        Get all messages without clearing the buffer.

        Returns
        -------
        List[str]
            A list of all buffered messages.
        """
        return self.messages.copy()


class GuptLogger:
    """
    Enhanced logger implementation with improved type safety and additional features.

    Parameters
    ----------
    config : LoggerConfig
        Configuration object containing logger settings.
    """

    _LOG_LEVEL_MAPPING: typing.ClassVar[
        typing.Dict[
            LogLevel,
            typing.Set[LogType],
        ]
    ] = {
        LogLevel.NO_ERROR: set(),
        LogLevel.BASIC: {
            LogType.INFO,
            LogType.WARNING,
        },
        LogLevel.STANDARD: {
            LogType.INFO,
            LogType.WARNING,
            LogType.ERROR,
        },
        LogLevel.DEBUG: {
            LogType.INFO,
            LogType.WARNING,
            LogType.DEBUG,
            LogType.ERROR,
        },
    }

    def __init__(
        self,
        config: LoggerConfig,
    ) -> None:
        """
        Initialize the GuptLogger with a given configuration.

        Parameters
        ----------
        config : LoggerConfig
            The logger configuration.
        """
        self.config = config
        self.log_file_path: typing.Optional[pathlib.Path] = (
            pathlib.Path(config.log_file) if config.log_file else None
        )
        self.colors = LogColor()
        self.buffer = LogBuffer(config.buffer_size)
        self._validate_config()
        self.log(
            message="Logger initialized successfully",
            log_type=LogType.DEBUG,
        )

    def _validate_config(
        self,
    ) -> None:
        """
        Validate logger configuration.

        Raises
        ------
        ValueError
            If the logger name is not specified or the log file directory does not exist.
        """
        if not self.config.name:
            raise ValueError("Logger name must be specified")
        if self.log_file_path and not self.log_file_path.parent.exists():
            raise ValueError(
                f"Log file directory does not exist: {self.log_file_path.parent}"
            )

    def _get_timestamp(
        self,
    ) -> str:
        """
        Get formatted timestamp string.

        Returns
        -------
        str
            The formatted timestamp, optionally colored.
        """
        timestamp = (
            datetime.datetime.now()
            .astimezone()
            .strftime(self.config.timestamp_format)
        )
        if self.config.enable_colors:
            return f"{self.colors.TIMESTAMP}{timestamp}{self.colors.RESET}"
        return timestamp

    def _format_message(
        self,
        message: str,
        log_type: LogType,
    ) -> str:
        """
        Format log message with appropriate colors and prefixes.

        Parameters
        ----------
        message : str
            The message to log.
        log_type : LogType
            The type of log message.

        Returns
        -------
        str
            The formatted log message.
        """
        color = getattr(
            self.colors,
            log_type.name,
        )
        timestamp = self._get_timestamp()
        if self.config.enable_colors:
            return (
                f"{self.config.name}: {timestamp} - {log_type.value.capitalize()} - "
                f"{color}{message}{self.colors.RESET}"
            )
        return f"{self.config.name}: {timestamp} - {log_type.value.capitalize()} - {message}"

    def _should_log(
        self,
        log_type: LogType,
    ) -> bool:
        """
        Determine if the message should be logged based on the current log level.

        Parameters
        ----------
        log_type : LogType
            The type of log message.

        Returns
        -------
        bool
            True if the message should be logged, False otherwise.
        """
        allowed_types = self._LOG_LEVEL_MAPPING.get(
            self.config.level,
            set(),
        )
        return log_type in allowed_types

    def _write_to_file(
        self,
        formatted_message: str,
    ) -> None:
        """
        Write the formatted message to the log file if one is configured.

        Parameters
        ----------
        formatted_message : str
            The formatted log message.
        """
        if not self.log_file_path:
            return
        try:
            os.makedirs(self.log_file_path.parent, exist_ok=True)
            with open(
                self.log_file_path,
                "a",
                encoding="utf-8",
            ) as file:
                file.write(f"{formatted_message}\n")
        except IOError as error:
            sys.stderr.write(f"Failed to write to log file: {error}\n")

    def log(
        self,
        message: str,
        log_type: LogType,
    ) -> None:
        """
        Log a message with the specified log type.

        Parameters
        ----------
        message : str
            The message to log.
        log_type : LogType
            The type of log message.
        """
        if not self._should_log(log_type):
            return

        formatted_message = self._format_message(
            message,
            log_type,
        )

        # Add to buffer
        self.buffer.add(formatted_message)

        # Write to console
        if self.config.clean_logs:
            sys.stdout.write(f"{formatted_message}\n")
            sys.stdout.flush()
        else:
            print(formatted_message)

        # Write to file if configured
        self._write_to_file(formatted_message)

        # Auto-flush the buffer if enabled and full
        if (
            self.config.auto_flush
            and len(self.buffer.messages) >= self.config.buffer_size
        ):
            self.flush_buffer()

    def flush_buffer(
        self,
    ) -> typing.List[str]:
        """
        Flush the log buffer and return all messages.

        Returns
        -------
        List[str]
            A list of the flushed log messages.
        """
        return self.buffer.flush()

    def get_logs(
        self,
    ) -> typing.List[str]:
        """
        Retrieve all logged messages without clearing the buffer.

        Returns
        -------
        List[str]
            A list of all current log messages in the buffer.
        """
        return self.buffer.get_all()

    @staticmethod
    def clear_console() -> None:
        """Clear the console screen."""
        sys.stdout.write("\033[H\033[J")
        sys.stdout.flush()
