"""
Сервис для проверки пользователей, заблокировавших бота.

Проверяет возможность отправки сообщений пользователям и позволяет
очистить БД и панель Remnawave от неактивных пользователей.
"""

import asyncio
from collections.abc import Callable
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum

import structlog
from aiogram import Bot
from aiogram.exceptions import TelegramAPIError, TelegramBadRequest, TelegramForbiddenError
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.database.models import (
    AdvertisingCampaignRegistration,
    ButtonClickLog,
    CabinetRefreshToken,
    CloudPaymentsPayment,
    ContestAttempt,
    CryptoBotPayment,
    DiscountOffer,
    FreekassaPayment,
    HeleketPayment,
    KassaAiPayment,
    MulenPayPayment,
    Pal24Payment,
    PlategaPayment,
    PollResponse,
    PromoCodeUse,
    ReferralContestEvent,
    ReferralEarning,
    SentNotification,
    Subscription,
    SubscriptionConversion,
    SubscriptionEvent,
    SubscriptionServer,
    Ticket,
    TicketMessage,
    TicketNotification,
    Transaction,
    User,
    UserPromoGroup,
    UserStatus,
    WataPayment,
    WheelSpin,
    WithdrawalRequest,
    YooKassaPayment,
)
from app.services.remnawave_service import RemnaWaveService


logger = structlog.get_logger(__name__)


class BlockCheckStatus(Enum):
    """Статус проверки блокировки пользователя."""

    BLOCKED = 'blocked'
    ACTIVE = 'active'
    NO_TELEGRAM_ID = 'no_telegram_id'
    ERROR = 'error'


class BlockedUserAction(Enum):
    """Действия над заблокированными пользователями."""

    DELETE_FROM_DB = 'delete_from_db'
    DELETE_FROM_REMNAWAVE = 'delete_from_remnawave'
    DELETE_BOTH = 'delete_both'
    MARK_AS_BLOCKED = 'mark_as_blocked'


@dataclass
class BlockCheckResult:
    """Результат проверки одного пользователя."""

    user_id: int
    telegram_id: int | None
    username: str | None
    full_name: str
    status: BlockCheckStatus
    error_message: str | None = None
    remnawave_uuid: str | None = None


@dataclass
class BlockedUsersScanResult:
    """Результат сканирования пользователей на блокировку."""

    total_checked: int = 0
    blocked_users: list[BlockCheckResult] = field(default_factory=list)
    active_users: int = 0
    errors: int = 0
    skipped_no_telegram: int = 0
    scan_duration_seconds: float = 0.0

    @property
    def blocked_count(self) -> int:
        return len(self.blocked_users)


@dataclass
class CleanupResult:
    """Результат очистки заблокированных пользователей."""

    deleted_from_db: int = 0
    deleted_from_remnawave: int = 0
    marked_as_blocked: int = 0
    errors: list[str] = field(default_factory=list)


class BlockedUsersService:
    """Сервис проверки и очистки заблокированных пользователей."""

    # Задержка между проверками для избежания rate limit
    CHECK_DELAY_SECONDS: float = 0.05
    # Максимальное количество параллельных проверок
    MAX_CONCURRENT_CHECKS: int = 10
    # Задержка между API запросами к Remnawave (rate limit protection)
    API_DELAY_SECONDS: float = 0.15

    def __init__(self, bot: Bot):
        self.bot = bot
        self.remnawave_service = RemnaWaveService()

    async def check_user_blocked(self, telegram_id: int) -> BlockCheckStatus:
        """
        Проверяет, заблокировал ли пользователь бота.

        Отправляет ChatAction.TYPING - это не создает видимого сообщения,
        но позволяет определить блокировку.
        """
        try:
            await self.bot.send_chat_action(chat_id=telegram_id, action='typing')
            return BlockCheckStatus.ACTIVE
        except TelegramForbiddenError:
            # Пользователь заблокировал бота
            return BlockCheckStatus.BLOCKED
        except TelegramBadRequest as e:
            error_lower = str(e).lower()
            if 'chat not found' in error_lower or 'user not found' in error_lower:
                # Пользователь удалил аккаунт или никогда не начинал диалог
                return BlockCheckStatus.BLOCKED
            logger.warning('TelegramBadRequest при проверке', telegram_id=telegram_id, error=e)
            return BlockCheckStatus.ERROR
        except TelegramAPIError as e:
            logger.warning('TelegramAPIError при проверке', telegram_id=telegram_id, error=e)
            return BlockCheckStatus.ERROR
        except Exception as e:
            logger.error('Неожиданная ошибка при проверке', telegram_id=telegram_id, error=e)
            return BlockCheckStatus.ERROR

    async def _check_single_user(self, user: User) -> BlockCheckResult:
        """Проверяет одного пользователя."""
        if not user.telegram_id:
            return BlockCheckResult(
                user_id=user.id,
                telegram_id=None,
                username=user.username,
                full_name=user.full_name,
                status=BlockCheckStatus.NO_TELEGRAM_ID,
                remnawave_uuid=user.remnawave_uuid,
            )

        status = await self.check_user_blocked(user.telegram_id)

        return BlockCheckResult(
            user_id=user.id,
            telegram_id=user.telegram_id,
            username=user.username,
            full_name=user.full_name,
            status=status,
            remnawave_uuid=user.remnawave_uuid,
        )

    async def scan_all_users(
        self,
        db: AsyncSession,
        *,
        only_active: bool = True,
        batch_size: int = 100,
        progress_callback: Callable | None = None,
    ) -> BlockedUsersScanResult:
        """
        Сканирует всех пользователей на предмет блокировки бота.

        Args:
            db: Сессия БД
            only_active: Проверять только активных пользователей
            batch_size: Размер батча для загрузки из БД
            progress_callback: Callback для отчета о прогрессе (checked, total)

        Returns:
            Результат сканирования
        """
        start_time = datetime.now(tz=UTC)
        result = BlockedUsersScanResult()

        # Формируем запрос
        query = select(User).options(selectinload(User.subscription))
        if only_active:
            query = query.where(User.status == UserStatus.ACTIVE.value)
        query = query.where(User.telegram_id.isnot(None))

        # Получаем всех пользователей
        users_result = await db.execute(query)
        all_users = users_result.scalars().all()
        total_users = len(all_users)

        logger.info('Начинаем проверку пользователей на блокировку бота', total_users=total_users)

        # Проверяем пользователей батчами с ограничением параллелизма
        semaphore = asyncio.Semaphore(self.MAX_CONCURRENT_CHECKS)

        async def check_with_semaphore(user: User) -> BlockCheckResult:
            async with semaphore:
                check_result = await self._check_single_user(user)
                await asyncio.sleep(self.CHECK_DELAY_SECONDS)
                return check_result

        checked = 0
        for i in range(0, total_users, batch_size):
            batch = all_users[i : i + batch_size]
            tasks = [check_with_semaphore(user) for user in batch]
            batch_results = await asyncio.gather(*tasks, return_exceptions=True)

            for check_result in batch_results:
                if isinstance(check_result, Exception):
                    result.errors += 1
                    logger.error('Ошибка при проверке пользователя', check_result=check_result)
                    continue

                result.total_checked += 1

                if check_result.status == BlockCheckStatus.BLOCKED:
                    result.blocked_users.append(check_result)
                elif check_result.status == BlockCheckStatus.ACTIVE:
                    result.active_users += 1
                elif check_result.status == BlockCheckStatus.NO_TELEGRAM_ID:
                    result.skipped_no_telegram += 1
                else:
                    result.errors += 1

            checked += len(batch)
            if progress_callback:
                await progress_callback(checked, total_users)

        result.scan_duration_seconds = (datetime.now(tz=UTC) - start_time).total_seconds()

        logger.info(
            'Сканирование завершено: заблокированных из проверенных за с',
            blocked_count=result.blocked_count,
            total_checked=result.total_checked,
            scan_duration_seconds=round(result.scan_duration_seconds, 1),
        )

        return result

    async def delete_user_from_remnawave(self, remnawave_uuid: str) -> bool:
        """Удаляет пользователя из панели Remnawave."""
        if not remnawave_uuid:
            return False

        try:
            if not self.remnawave_service.is_configured:
                logger.warning('Remnawave API не настроен')
                return False

            async with self.remnawave_service.get_api_client() as api:
                await api.delete_user(remnawave_uuid)
                logger.info('Удален пользователь из Remnawave', remnawave_uuid=remnawave_uuid)
                return True
        except Exception as e:
            error_msg = str(e).lower()
            if 'not found' in error_msg or '404' in error_msg:
                logger.info('Пользователь уже удален из Remnawave', remnawave_uuid=remnawave_uuid)
                return True
            logger.error('Ошибка удаления из Remnawave', remnawave_uuid=remnawave_uuid, error=e)
            return False

    async def delete_user_from_db(self, db: AsyncSession, user_id: int) -> bool:
        """
        Полностью удаляет пользователя из БД со всеми связанными данными.
        """
        try:
            # Получаем пользователя
            user_result = await db.execute(
                select(User).options(selectinload(User.subscription)).where(User.id == user_id)
            )
            user = user_result.scalar_one_or_none()

            if not user:
                logger.warning('Пользователь не найден в БД', user_id=user_id)
                return False

            user_display = user.telegram_id or user.email or f'#{user.id}'

            # Удаляем связанные записи (порядок важен из-за foreign keys)

            # 1. Платежные системы (до транзакций, т.к. ссылаются на них)
            await db.execute(delete(YooKassaPayment).where(YooKassaPayment.user_id == user.id))
            await db.execute(delete(CryptoBotPayment).where(CryptoBotPayment.user_id == user.id))
            await db.execute(delete(HeleketPayment).where(HeleketPayment.user_id == user.id))
            await db.execute(delete(MulenPayPayment).where(MulenPayPayment.user_id == user.id))
            await db.execute(delete(Pal24Payment).where(Pal24Payment.user_id == user.id))
            await db.execute(delete(WataPayment).where(WataPayment.user_id == user.id))
            await db.execute(delete(PlategaPayment).where(PlategaPayment.user_id == user.id))
            await db.execute(delete(CloudPaymentsPayment).where(CloudPaymentsPayment.user_id == user.id))
            await db.execute(delete(FreekassaPayment).where(FreekassaPayment.user_id == user.id))
            await db.execute(delete(KassaAiPayment).where(KassaAiPayment.user_id == user.id))

            # 2. Транзакции (после платежей)
            await db.execute(delete(Transaction).where(Transaction.user_id == user.id))

            # 3. Подписки
            if user.subscription:
                await db.execute(
                    delete(SubscriptionServer).where(SubscriptionServer.subscription_id == user.subscription.id)
                )
                await db.execute(delete(Subscription).where(Subscription.user_id == user.id))
            await db.execute(delete(SubscriptionConversion).where(SubscriptionConversion.user_id == user.id))
            await db.execute(delete(SubscriptionEvent).where(SubscriptionEvent.user_id == user.id))

            # 4. Тикеты (сначала зависимые)
            await db.execute(delete(TicketNotification).where(TicketNotification.user_id == user.id))
            await db.execute(delete(TicketMessage).where(TicketMessage.user_id == user.id))
            await db.execute(delete(Ticket).where(Ticket.user_id == user.id))

            # 5. Остальные связи
            await db.execute(delete(ReferralEarning).where(ReferralEarning.user_id == user.id))
            await db.execute(delete(ReferralEarning).where(ReferralEarning.referral_id == user.id))
            await db.execute(delete(WithdrawalRequest).where(WithdrawalRequest.user_id == user.id))
            await db.execute(delete(PromoCodeUse).where(PromoCodeUse.user_id == user.id))
            await db.execute(delete(DiscountOffer).where(DiscountOffer.user_id == user.id))
            await db.execute(delete(SentNotification).where(SentNotification.user_id == user.id))
            await db.execute(delete(PollResponse).where(PollResponse.user_id == user.id))
            await db.execute(delete(ContestAttempt).where(ContestAttempt.user_id == user.id))
            await db.execute(delete(ReferralContestEvent).where(ReferralContestEvent.referrer_id == user.id))
            await db.execute(delete(ReferralContestEvent).where(ReferralContestEvent.referral_id == user.id))
            await db.execute(
                delete(AdvertisingCampaignRegistration).where(AdvertisingCampaignRegistration.user_id == user.id)
            )
            await db.execute(delete(UserPromoGroup).where(UserPromoGroup.user_id == user.id))
            await db.execute(delete(CabinetRefreshToken).where(CabinetRefreshToken.user_id == user.id))
            await db.execute(delete(ButtonClickLog).where(ButtonClickLog.user_id == user.id))
            await db.execute(delete(WheelSpin).where(WheelSpin.user_id == user.id))

            # Обнуляем referred_by_id у рефералов этого пользователя
            referrals_query = select(User).where(User.referred_by_id == user.id)
            referrals_result = await db.execute(referrals_query)
            for referral in referrals_result.scalars().all():
                referral.referred_by_id = None

            # Удаляем пользователя
            await db.delete(user)
            await db.commit()

            logger.info('Пользователь полностью удален из БД', user_display=user_display)
            return True

        except Exception as e:
            logger.error('Ошибка удаления пользователя из БД', user_id=user_id, error=e)
            await db.rollback()
            return False

    async def mark_user_as_blocked(self, db: AsyncSession, user_id: int) -> bool:
        """Помечает пользователя как заблокированного в БД."""
        try:
            user_result = await db.execute(select(User).where(User.id == user_id))
            user = user_result.scalar_one_or_none()

            if not user:
                return False

            user.status = UserStatus.BLOCKED.value
            user.updated_at = datetime.now(tz=UTC)
            await db.commit()

            logger.info('Пользователь помечен как заблокированный', telegram_id=user.telegram_id or user.id)
            return True

        except Exception as e:
            logger.error('Ошибка пометки пользователя', user_id=user_id, error=e)
            await db.rollback()
            return False

    async def cleanup_blocked_users(
        self,
        db: AsyncSession,
        blocked_users: list[BlockCheckResult],
        action: BlockedUserAction,
        *,
        progress_callback: Callable | None = None,
    ) -> CleanupResult:
        """
        Выполняет очистку заблокированных пользователей.

        Args:
            db: Сессия БД
            blocked_users: Список заблокированных пользователей
            action: Действие для выполнения
            progress_callback: Callback для отчета о прогрессе

        Returns:
            Результат очистки
        """
        result = CleanupResult()
        total = len(blocked_users)

        for i, user_result in enumerate(blocked_users):
            try:
                if action in (BlockedUserAction.DELETE_FROM_REMNAWAVE, BlockedUserAction.DELETE_BOTH):
                    if user_result.remnawave_uuid:
                        success = await self.delete_user_from_remnawave(user_result.remnawave_uuid)
                        if success:
                            result.deleted_from_remnawave += 1
                        else:
                            result.errors.append(f'Ошибка удаления {user_result.telegram_id} из Remnawave')
                        # Задержка для избежания rate limit
                        await asyncio.sleep(self.API_DELAY_SECONDS)

                if action in (BlockedUserAction.DELETE_FROM_DB, BlockedUserAction.DELETE_BOTH):
                    success = await self.delete_user_from_db(db, user_result.user_id)
                    if success:
                        result.deleted_from_db += 1
                    else:
                        result.errors.append(f'Ошибка удаления {user_result.telegram_id} из БД')

                if action == BlockedUserAction.MARK_AS_BLOCKED:
                    success = await self.mark_user_as_blocked(db, user_result.user_id)
                    if success:
                        result.marked_as_blocked += 1
                    else:
                        result.errors.append(f'Ошибка пометки {user_result.telegram_id}')

                if progress_callback:
                    await progress_callback(i + 1, total)

            except Exception as e:
                error_msg = f'Ошибка обработки {user_result.telegram_id}: {e}'
                result.errors.append(error_msg)
                logger.error(error_msg)

        return result
