from pathlib import Path
from functools import wraps
from sys import stderr as console
from inspect import iscoroutinefunction
from typing import Any, Callable, Optional, TypeVar, cast, Final

from loguru import logger as logs
from aiogram.types import Message, User

from configs.config import BotEdit, LogConfig

# Экспортируемые объекты
__all__ = ('Logger', 'logger', 'log',)

# Универсальный тип для функций
F: TypeVar = TypeVar('F', bound=Callable[..., Any])


class Logger:
    """
    Кастомный логгер с поддержкой декораторов и прямого вызова.

    Attributes:
        system_name: Имя системы для логирования
        _log_format: Формат логов
    """
    _log_format: Final[str] = (
        '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> <red>|</red> '
        '<blue>{extra[system]}-{extra[log_type]}</blue> <red>| '
        '{extra[user]} |</red> <level>{message}</level>'
    )

    def __init__(self, system_name: str = BotEdit.PROJECT_NAME) -> None:
        """
        Инициализация логгера.

        :param system_name: Имя системы для логирования
        """
        self.system_name = system_name
        self._setup_done = False

    def setup(self, start: bool = True) -> None:
        """
        Настройка обработчиков Loguru: консоль и файлы.

        :param start: Если True, сразу логирует запуск проекта
        """
        if self._setup_done:
            return

        # Полная очистка настроек
        logs.remove()

        # Создание директории для файловых логов
        log_dir: Path = Path(getattr(LogConfig, 'DIR', 'logs'))
        log_dir.mkdir(parents=True, exist_ok=True)

        # Консольный лог
        if getattr(LogConfig, 'CONSOLE', False):
            logs.add(
                sink=console,
                format=self._log_format,
                colorize=True,
                level='DEBUG',
                filter=lambda rec: rec['extra'].get('log_type') != 'DEBUG'
            )

        # Файловые логи
        if getattr(LogConfig, 'FILE', False):
            # Общий лог
            logs.add(
                sink=log_dir / 'bot.log',
                rotation=getattr(LogConfig, 'ROTATION', '100 MB'),
                retention=getattr(LogConfig, 'RETENTION', '7 days'),
                format=self._log_format,
                level='DEBUG',
                enqueue=True,
                backtrace=True,
                diagnose=True
            )
            # Раздельные логи по уровням
            for level_name in ['INFO', 'WARNING', 'ERROR', 'DEBUG', 'CRITICAL']:
                logs.add(
                    sink=log_dir / f'{level_name.lower()}.log',
                    rotation='10 MB',
                    retention='7 days',
                    format=self._log_format,
                    level=level_name,
                    filter=lambda rec, lvl=level_name: rec['level'].name == lvl,
                    enqueue=True
                )

        self._setup_done = True

        # Логируем старт
        if start:
            self.log_entry(
                level='INFO',
                text='Запуск проекта...',
                log_type='START'
            )

    @staticmethod
    def format_user(message: Optional[Message] = None) -> str:
        """
        Форматирует имя пользователя из объекта Message.

        :param message: Объект aiogram.types.Message
        :return: Строка '@username' или 'id<user_id>'
        """
        if message is None or message.from_user is None:
            return '@System'
        user: User = message.from_user
        return f"@{user.username}" if user.username else f"id{user.id}"

    def log_entry(
            self,
            level: str,
            text: str,
            log_type: str,
            user: Optional[str] = None,
            message: Optional[Message] = None
    ) -> None:
        """
        Основной метод для записи логов.

        :param level: Уровень логирования (например, 'INFO')
        :param text: Сообщение для логирования
        :param log_type: Кастомный тип лога (например, 'HANDLER')
        :param user: Явно указанный пользователь
        :param message: Объект Message для извлечения юзера
        """
        actual_user: str = user or self.format_user(message)
        logs.bind(
            system=self.system_name,
            user=actual_user,
            log_type=log_type
        ).log(level, text)

    def log(
            self,
            level: str = 'INFO',
            log_type: str = '',
            text: Optional[str] = None
    ) -> Callable[[F], F]:
        """
        Декоратор для логирования функций.

        :param level: Уровень логирования
        :param log_type: Категория лога
        :param text: Кастомный текст сообщения
        :return: Декорированную функцию
        """

        def decorator(func: F) -> F:
            is_coroutine = iscoroutinefunction(func)
            action_text = text or f'Вызов {func.__name__}'

            @wraps(func)
            def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
                message = self._find_message(args)
                self.log_entry(level, f"[START] {action_text}", log_type, message=message)
                try:
                    result = func(*args, **kwargs)
                    self.log_entry(level, f"[SUCCESS] {action_text}", log_type, message=message)
                    return result
                except Exception as e:
                    self.log_entry(
                        'ERROR',
                        f"[ERROR] {action_text} | Exception: {e!r}",
                        log_type,
                        message=message
                    )
                    raise

            @wraps(func)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                message = self._find_message(args)
                self.log_entry(level, f"[START] {action_text}", log_type, message=message)
                try:
                    result = await func(*args, **kwargs)
                    self.log_entry(level, f"[SUCCESS] {action_text}", log_type, message=message)
                    return result
                except Exception as e:
                    self.log_entry(
                        'ERROR',
                        f"[ERROR] {action_text} | Exception: {e!r}",
                        log_type,
                        message=message
                    )
                    raise

            return cast(F, async_wrapper if is_coroutine else sync_wrapper)

        return decorator

    @staticmethod
    def _find_message(args: tuple[Any, ...]) -> Optional[Message]:
        """
        Ищет объект Message в аргументах функции.

        :param args: Аргументы функции
        :return: Найденный Message или None
        """
        return next((arg for arg in args if isinstance(arg, Message)), None)

    # Методы для прямого вызова
    def debug(self, text: str, log_type: str = 'BOT', user: Optional[str] = None,
              message: Optional[Message] = None) -> None:
        self.log_entry('DEBUG', text, log_type, user, message)

    def info(self, text: str, log_type: str = 'BOT', user: Optional[str] = None,
             message: Optional[Message] = None) -> None:
        self.log_entry('INFO', text, log_type, user, message)

    def warning(self, text: str, log_type: str = 'BOT', user: Optional[str] = None,
                message: Optional[Message] = None) -> None:
        self.log_entry('WARNING', text, log_type, user, message)

    def error(self, text: str, log_type: str = 'BOT', user: Optional[str] = None,
              message: Optional[Message] = None) -> None:
        self.log_entry('ERROR', text, log_type, user, message)

    def critical(self, text: str, log_type: str = 'BOT', user: Optional[str] = None,
                 message: Optional[Message] = None) -> None:
        self.log_entry('CRITICAL', text, log_type, user, message)


# Создаем глобальный экземпляр логгера
logger: Logger = Logger()

# Экспортируемые функции для обратной совместимости
log = logger.log
