"""
Middleware для глобальной обработки ошибок
"""
from typing import Callable, Awaitable, Any, Dict, Optional, List, Set
from datetime import datetime
from collections import defaultdict
from enum import Enum
import traceback

from aiogram import BaseMiddleware
from aiogram.types import TelegramObject, Message, CallbackQuery, Update
from aiogram.exceptions import (
    TelegramBadRequest,
    TelegramForbiddenError,
    TelegramNotFound,
    TelegramUnauthorizedError,
    TelegramRetryAfter,
    TelegramAPIError
)

from middleware.loggers import logger
from bot.utils import (
    username,
    format_content_info,
    get_content_type,
    safe_answer_callback,
    format_duration,
    format_timestamp
)
from bot.templates import msg

__all__ = ('ErrorHandlingMiddleware', 'ErrorCategory')


class ErrorCategory(str, Enum):
    """Категории ошибок"""
    TELEGRAM_API = "telegram_api"  # Ошибки Telegram API
    RATE_LIMIT = "rate_limit"  # Rate limiting
    PERMISSION = "permission"  # Права доступа
    VALIDATION = "validation"  # Валидация данных
    DATABASE = "database"  # Ошибки БД
    HANDLER = "handler"  # Ошибки в хендлерах
    UNKNOWN = "unknown"  # Неизвестные ошибки


class ErrorStats:
    """Статистика ошибок"""

    def __init__(self):
        # Счетчики по категориям
        self.by_category: Dict[ErrorCategory, int] = defaultdict(int)

        # Счетчики по типам исключений
        self.by_exception: Dict[str, int] = defaultdict(int)

        # Последние ошибки (последние 10)
        self.recent_errors: List[Dict[str, Any]] = []
        self.max_recent = 10

        # Общая статистика
        self.total_errors: int = 0
        self.start_time: datetime = datetime.now()

    def add_error(
            self,
            exception: Exception,
            category: ErrorCategory,
            user_id: Optional[int] = None,
            details: Optional[Dict] = None
    ):
        """Добавляет ошибку в статистику"""
        self.total_errors += 1
        self.by_category[category] += 1
        self.by_exception[type(exception).__name__] += 1

        # Добавляем в последние ошибки
        error_info = {
            'timestamp': datetime.now(),
            'exception': type(exception).__name__,
            'message': str(exception),
            'category': category,
            'user_id': user_id,
            'details': details or {}
        }

        self.recent_errors.append(error_info)
        if len(self.recent_errors) > self.max_recent:
            self.recent_errors.pop(0)

    def get_summary(self) -> Dict[str, Any]:
        """Возвращает сводку по статистике"""
        uptime = datetime.now() - self.start_time

        return {
            'total_errors': self.total_errors,
            'uptime': format_duration(int(uptime.total_seconds())),
            'by_category': dict(self.by_category),
            'by_exception': dict(self.by_exception),
            'recent_errors': self.recent_errors
        }


class ErrorHandlingMiddleware(BaseMiddleware):
    """
    Middleware для глобальной обработки ошибок.

    Features:
        - Категоризация ошибок
        - Уведомление администраторов
        - Статистика ошибок
        - Rate limiting уведомлений
        - Retry механизм для некоторых ошибок
        - Детальное логирование
        - Graceful degradation
    """

    def __init__(
            self,
            admin_ids: List[int],
            notify_admins: bool = True,
            notify_users: bool = True,
            log_errors: bool = True,
            notify_rate_limit: int = 60  # Не чаще раза в минуту для одного типа ошибки
    ):
        """
        Args:
            admin_ids: Список ID администраторов
            notify_admins: Уведомлять администраторов
            notify_users: Уведомлять пользователей
            log_errors: Логировать ошибки
            notify_rate_limit: Минимальный интервал между уведомлениями (секунды)
        """
        super().__init__()
        self.admin_ids = admin_ids
        self.notify_admins = notify_admins
        self.notify_users = notify_users
        self.log_errors = log_errors
        self.notify_rate_limit = notify_rate_limit

        # Статистика
        self.stats = ErrorStats()

        # Rate limiting для уведомлений
        # {error_type: last_notification_time}
        self._last_notifications: Dict[str, datetime] = {}

        # Игнорируемые ошибки (для которых не нужно уведомлять)
        self.ignored_errors: Set[type] = {
            TelegramRetryAfter,  # Rate limit Telegram
        }

    async def __call__(
            self,
            handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
            event: TelegramObject,
            data: Dict[str, Any]
    ) -> Any:
        """
        Обрабатывает ошибки в хендлерах.

        Args:
            handler: Следующий обработчик
            event: Входящее событие
            data: Контекстные данные

        Returns:
            Результат выполнения обработчика или None при ошибке
        """
        try:
            # Выполняем хендлер
            return await handler(event, data)

        except Exception as e:
            # Обрабатываем ошибку
            await self._handle_error(e, event, data)
            return None

    async def _handle_error(
            self,
            exception: Exception,
            event: TelegramObject,
            data: Dict[str, Any]
    ):
        """
        Централизованная обработка ошибки.

        Args:
            exception: Исключение
            event: Событие
            data: Контекстные данные
        """
        # Определяем категорию ошибки
        category = self._categorize_error(exception)

        # Извлекаем информацию о событии
        event_info = self._extract_event_info(event)

        # Добавляем в статистику
        self.stats.add_error(
            exception=exception,
            category=category,
            user_id=event_info.get('user_id'),
            details=event_info
        )

        # Логируем ошибку
        if self.log_errors:
            await self._log_error(exception, category, event_info)

        # Уведомляем администраторов
        if self.notify_admins and not self._is_ignored(exception):
            await self._notify_admins_about_error(exception, category, event_info, event)

        # Уведомляем пользователя
        if self.notify_users:
            await self._notify_user_about_error(exception, category, event)

    @staticmethod
    def _categorize_error(exception: Exception) -> ErrorCategory:
        """
        Определяет категорию ошибки.

        Args:
            exception: Исключение

        Returns:
            Категория ошибки
        """
        # Ошибки Telegram API
        if isinstance(exception, TelegramRetryAfter):
            return ErrorCategory.RATE_LIMIT

        if isinstance(exception, (TelegramForbiddenError, TelegramUnauthorizedError)):
            return ErrorCategory.PERMISSION

        if isinstance(exception, (TelegramBadRequest, TelegramNotFound)):
            return ErrorCategory.TELEGRAM_API

        if isinstance(exception, TelegramAPIError):
            return ErrorCategory.TELEGRAM_API

        # Ошибки валидации
        if isinstance(exception, (ValueError, TypeError, AttributeError)):
            return ErrorCategory.VALIDATION

        # Ошибки БД (примеры, замени на свои)
        # if isinstance(exception, (DatabaseError, OperationalError)):
        #     return ErrorCategory.DATABASE

        # Остальные ошибки
        return ErrorCategory.HANDLER

    @staticmethod
    def _extract_event_info(event: TelegramObject) -> Dict[str, Any]:
        """
        Извлекает информацию о событии.

        Args:
            event: Объект события

        Returns:
            Словарь с информацией
        """
        info: Dict[str, Any] = {
            'event_type': type(event).__name__,
            'timestamp': datetime.now(),
            'user_str': '@System',
            'user_id': None,
            'chat_id': None,
            'chat_type': None,
            'message_id': None,
            'content_type': None,
            'content_info': None,
            'text': None
        }

        # Обработка разных типов событий
        message = None

        if isinstance(event, Message):
            message = event
        elif isinstance(event, CallbackQuery):
            message = event.message
            info['callback_data'] = event.data
        elif isinstance(event, Update):
            message = (
                    event.message or
                    event.edited_message or
                    event.channel_post or
                    event.edited_channel_post
            )

            if event.callback_query:
                info['callback_data'] = event.callback_query.data

        # Извлекаем информацию из сообщения
        if message:
            # Пользователь
            if message.from_user:
                info['user_str'] = username(message)
                info['user_id'] = message.from_user.id

            # Чат
            info['chat_id'] = message.chat.id
            info['chat_type'] = message.chat.type
            info['message_id'] = message.message_id

            # Контент
            info['content_type'] = get_content_type(message)
            info['content_info'] = format_content_info(message, include_text=False)

            # Текст
            if message.text:
                text = message.text
                info['text'] = text if len(text) <= 100 else text[:100] + "..."
            elif message.caption:
                caption = message.caption
                info['caption'] = caption if len(caption) <= 100 else caption[:100] + "..."

        return info

    @staticmethod
    async def _log_error(
            exception: Exception,
            category: ErrorCategory,
            event_info: Dict[str, Any]
    ):
        """
        Логирует ошибку.

        Args:
            exception: Исключение
            category: Категория ошибки
            event_info: Информация о событии
        """
        # Формируем сообщение для лога
        error_type = type(exception).__name__
        error_msg = str(exception)

        # Получаем traceback
        tb = ''.join(traceback.format_exception(
            type(exception),
            exception,
            exception.__traceback__
        ))

        # Базовое сообщение
        log_msg = (
            f"🚨 Ошибка в хендлере\n"
            f"├─ Тип: {error_type}\n"
            f"├─ Категория: {category.value}\n"
            f"├─ Сообщение: {error_msg}\n"
            f"├─ Событие: {event_info['event_type']}\n"
        )

        if event_info.get('text'):
            log_msg += f"├─ Текст: {event_info['text']}\n"

        if event_info.get('callback_data'):
            log_msg += f"├─ Callback: {event_info['callback_data']}\n"

        if event_info.get('content_info'):
            log_msg += f"└─ Контент: {event_info['content_info']}"

        # Логируем с полным traceback
        logger.error(
            text=log_msg,
            log_type=f"ERROR_{category.value.upper()}",
            user=event_info['user_str'],
        )

        # Дополнительно логируем traceback отдельно для детального анализа
        logger.debug(
            text=f"Полный traceback:\n{tb}",
            log_type=f"ERROR_{category.value.upper()}_TRACEBACK",
            user=event_info['user_str']
        )

    async def _notify_admins_about_error(
            self,
            exception: Exception,
            category: ErrorCategory,
            event_info: Dict[str, Any],
            event: TelegramObject
    ):
        """
        Уведомляет администраторов об ошибке.

        Args:
            exception: Исключение
            category: Категория ошибки
            event_info: Информация о событии
            event: Объект события
        """
        # Проверяем rate limit
        error_key = type(exception).__name__

        if not self._should_notify(error_key):
            logger.debug(
                f"Пропуск уведомления админов о {error_key} (rate limit)",
                log_type="ADMIN_NOTIFY_SKIP"
            )
            return

        # Обновляем время последнего уведомления
        self._last_notifications[error_key] = datetime.now()

        # Получаем bot
        bot = event.bot if hasattr(event, 'bot') else None
        if not bot:
            return

        # Формируем сообщение
        error_type = type(exception).__name__
        error_msg = str(exception)

        # Определяем emoji для категории
        category_emoji = self._get_category_emoji(category)

        notification = (
            f"{category_emoji} <b>Ошибка в боте</b>\n\n"
            f"📊 <b>Информация:</b>\n"
            f"├─ Тип: <code>{error_type}</code>\n"
            f"├─ Категория: <code>{category.value}</code>\n"
            f"├─ Время: {format_timestamp(datetime.now())}\n"
        )

        # Добавляем информацию о пользователе
        if event_info.get('user_str') and event_info['user_str'] != '@System':
            notification += f"└─ Пользователь: {event_info['user_str']}\n\n"
        else:
            notification += "\n"

        # Добавляем сообщение ошибки
        if len(error_msg) <= 200:
            notification += f"💬 <b>Сообщение:</b>\n<code>{error_msg}</code>\n\n"
        else:
            notification += f"💬 <b>Сообщение:</b>\n<code>{error_msg[:200]}...</code>\n\n"

        # Добавляем контекст события
        notification += f"📋 <b>Контекст:</b>\n"

        if event_info.get('text'):
            notification += f"├─ Текст: <code>{event_info['text']}</code>\n"

        if event_info.get('callback_data'):
            notification += f"├─ Callback: <code>{event_info['callback_data']}</code>\n"

        if event_info.get('content_info'):
            notification += f"├─ Контент: {event_info['content_info']}\n"

        if event_info.get('chat_type'):
            notification += f"└─ Тип чата: <code>{event_info['chat_type']}</code>\n"

        # Добавляем статистику
        stats = self.stats.get_summary()
        notification += (
            f"\n📊 <b>Статистика:</b>\n"
            f"└─ Всего ошибок: {stats['total_errors']}"
        )

        # Отправляем администраторам
        for admin_id in self.admin_ids:
            try:
                await bot.send_message(
                    chat_id=admin_id,
                    text=notification,
                    parse_mode="HTML"
                )

                logger.debug(
                    f"Администратор {admin_id} уведомлен об ошибке",
                    log_type="ADMIN_NOTIFIED"
                )

            except Exception as e:
                logger.error(
                    f"Не удалось уведомить админа {admin_id}: {e}",
                    log_type="ADMIN_NOTIFY_ERROR"
                )

    @staticmethod
    async def _notify_user_about_error(
            exception: Exception,
            category: ErrorCategory,
            event: TelegramObject
    ):
        """
        Уведомляет пользователя об ошибке.

        Args:
            exception: Исключение
            category: Категория ошибки
            event: Объект события
        """
        # Формируем сообщение в зависимости от категории
        error_messages = {
            ErrorCategory.TELEGRAM_API: (
                "⚠️ Произошла техническая ошибка.\n"
                "Попробуйте повторить действие."
            ),
            ErrorCategory.RATE_LIMIT: (
                "⏳ Слишком много запросов.\n"
                "Пожалуйста, подождите немного."
            ),
            ErrorCategory.PERMISSION: (
                "🔒 Недостаточно прав для выполнения действия."
            ),
            ErrorCategory.VALIDATION: (
                "❌ Некорректные данные.\n"
                "Проверьте правильность ввода."
            ),
            ErrorCategory.DATABASE: (
                "💾 Ошибка базы данных.\n"
                "Попробуйте позже."
            ),
            ErrorCategory.HANDLER: (
                "⚠️ Произошла непредвиденная ошибка.\n"
                "Разработчики уже уведомлены."
            ),
            ErrorCategory.UNKNOWN: (
                "⚠️ Произошла ошибка.\n"
                "Попробуйте повторить позже."
            )
        }

        error_text = error_messages.get(
            category,
            error_messages[ErrorCategory.UNKNOWN]
        )

        error_text += "\n\nПопробуйте нажать /start или обратитесь к администратору."

        try:
            # Отправляем сообщение
            if isinstance(event, Message):
                await msg(event, text=error_text)

            elif isinstance(event, CallbackQuery):
                await safe_answer_callback(event, error_text[:200], show_alert=True)

                # Также отправляем в чат если сообщение доступно
                if event.message:
                    try:
                        await msg(event.message, text=error_text)
                    except:
                        pass

            elif isinstance(event, Update):
                if event.message:
                    await msg(event.message, text=error_text)
                elif event.callback_query:
                    await safe_answer_callback(
                        event.callback_query,
                        error_text[:200],
                        show_alert=True
                    )

            logger.debug(
                "Пользователь уведомлен об ошибке",
                log_type="USER_ERROR_NOTIFIED"
            )

        except Exception as e:
            logger.warning(
                f"Не удалось уведомить пользователя об ошибке: {e}",
                log_type="USER_NOTIFY_ERROR"
            )

    def _should_notify(self, error_key: str) -> bool:
        """
        Проверяет, нужно ли отправлять уведомление (rate limiting).

        Args:
            error_key: Ключ ошибки

        Returns:
            True если можно отправить уведомление
        """
        if error_key not in self._last_notifications:
            return True

        last_time = self._last_notifications[error_key]
        time_passed = (datetime.now() - last_time).total_seconds()

        return time_passed >= self.notify_rate_limit

    def _is_ignored(self, exception: Exception) -> bool:
        """
        Проверяет, игнорируется ли ошибка.

        Args:
            exception: Исключение

        Returns:
            True если ошибка игнорируется
        """
        return type(exception) in self.ignored_errors

    @staticmethod
    def _get_category_emoji(category: ErrorCategory) -> str:
        """Возвращает emoji для категории ошибки"""
        emoji_map = {
            ErrorCategory.TELEGRAM_API: "🔌",
            ErrorCategory.RATE_LIMIT: "⏳",
            ErrorCategory.PERMISSION: "🔒",
            ErrorCategory.VALIDATION: "❌",
            ErrorCategory.DATABASE: "💾",
            ErrorCategory.HANDLER: "🚨",
            ErrorCategory.UNKNOWN: "⚠️"
        }

        return emoji_map.get(category, "⚠️")

    def get_stats(self) -> Dict[str, Any]:
        """Возвращает статистику ошибок"""
        return self.stats.get_summary()

    def reset_stats(self):
        """Сбрасывает статистику"""
        self.stats = ErrorStats()

    def add_ignored_error(self, error_type: type):
        """Добавляет тип ошибки в игнорируемые"""
        self.ignored_errors.add(error_type)

    def remove_ignored_error(self, error_type: type):
        """Удаляет тип ошибки из игнорируемых"""
        self.ignored_errors.discard(error_type)


# ================= УТИЛИТЫ =================

def format_error_stats(stats: Dict[str, Any]) -> str:
    """
    Форматирует статистику ошибок.

    Args:
        stats: Словарь со статистикой

    Returns:
        Отформатированная строка

    Example:
        >> stats = middleware.get_stats()
        >> print(format_error_stats(stats))
    """
    text = (
        f"🚨 <b>Статистика ошибок</b>\n\n"
        f"📊 <b>Общая информация:</b>\n"
        f"├─ Всего ошибок: {stats['total_errors']}\n"
        f"└─ Время работы: {stats['uptime']}\n\n"
    )

    # По категориям
    if stats['by_category']:
        text += f"📁 <b>По категориям:</b>\n"
        for category, count in stats['by_category'].items():
            text += f"├─ {category}: {count}\n"
        text += "\n"

    # По типам исключений
    if stats['by_exception']:
        text += f"🔧 <b>По типам (топ-5):</b>\n"
        sorted_exceptions = sorted(
            stats['by_exception'].items(),
            key=lambda x: x[1],
            reverse=True
        )[:5]

        for exc_type, count in sorted_exceptions:
            text += f"├─ {exc_type}: {count}\n"

    return text
