"""Drop-in IHM protection for Telegram guest bots, for aiogram 3.30+.

Install this middleware before polling. It does not start its own polling and
does not interfere with a host bot's captcha or regular user handlers.
"""

from __future__ import annotations

import base64
import html
import logging
import re
import time
from collections import deque
from io import BytesIO
from typing import Any, Iterable

import aiohttp
from aiogram import BaseMiddleware, Bot, Dispatcher, F, Router
from aiogram.types import (
    BufferedInputFile,
    CallbackQuery,
    InlineKeyboardButton,
    InlineKeyboardMarkup,
    Message,
)


logger = logging.getLogger(__name__)
BOT_MENTION_RE = re.compile(r"(?<![\w@])@([A-Za-z0-9_]{4,31}bot)\b", re.I)


def incident_keyboard(incident_id: str) -> InlineKeyboardMarkup:
    incident_id = str(incident_id or "").strip()
    if not incident_id:
        raise ValueError("incident_id_required")
    skip = f"ihmg:skip:{incident_id}"
    ban = f"ihmg:ban:{incident_id}"
    if len(skip.encode()) > 64 or len(ban.encode()) > 64:
        raise ValueError("incident_id_too_long")
    return InlineKeyboardMarkup(
        inline_keyboard=[
            [
                InlineKeyboardButton(text="Пропустить", callback_data=skip),
                InlineKeyboardButton(text="Забанить", callback_data=ban),
            ]
        ]
    )


class IHMGuestBotGuard(BaseMiddleware):
    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = "https://ihm.thebeautifulmusic.ru",
        trusted_bot_ids: Iterable[int] = (),
        trusted_bot_usernames: Iterable[str] = (),
    ) -> None:
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.trusted_bot_ids = {int(value) for value in trusted_bot_ids}
        self.trusted_bot_usernames = {
            str(value).lstrip("@").casefold() for value in trusted_bot_usernames
        }
        self.recent_mentions: dict[
            tuple[int, str, int], deque[tuple[float, int]]
        ] = {}
        self._binding: dict[str, Any] | None = None
        self.router = Router(name="ihm_guest_bot_guard")
        self.router.callback_query.register(
            self._decision_callback, F.data.startswith("ihmg:skip:")
        )
        self.router.callback_query.register(
            self._decision_callback, F.data.startswith("ihmg:ban:")
        )

    async def _api(
        self, method: str, path: str, payload: dict[str, Any] | None = None
    ) -> dict[str, Any]:
        headers = {"Authorization": f"Bearer {self.api_key}"}
        timeout = aiohttp.ClientTimeout(total=60)
        async with aiohttp.ClientSession(timeout=timeout, headers=headers) as session:
            async with session.request(
                method, self.base_url + path, json=payload
            ) as response:
                response.raise_for_status()
                return await response.json()

    def _remember_mentions(self, message: Message) -> None:
        if not message.from_user or message.from_user.is_bot:
            return
        text = message.text or message.caption or ""
        now = time.monotonic()
        for username in BOT_MENTION_RE.findall(text):
            key = (message.chat.id, username.casefold(), message.from_user.id)
            items = self.recent_mentions.setdefault(key, deque(maxlen=20))
            items.append((now, message.message_id))

    def _trigger_message_id(self, message: Message) -> int:
        caller = message.guest_bot_caller_user
        sender = message.from_user
        if not caller or not sender:
            return 0
        if (
            message.reply_to_message
            and message.reply_to_message.from_user
            and message.reply_to_message.from_user.id == caller.id
        ):
            return message.reply_to_message.message_id
        key = (message.chat.id, (sender.username or "").casefold(), caller.id)
        items = self.recent_mentions.get(key)
        if not items:
            return 0
        cutoff = time.monotonic() - 120
        while items and items[0][0] < cutoff:
            items.popleft()
        return items[-1][1] if items else 0

    async def _avatar_base64(self, bot: Bot, user_id: int) -> str:
        try:
            photos = await bot.get_user_profile_photos(user_id=user_id, limit=1)
            if not photos.photos:
                return ""
            telegram_file = await bot.get_file(photos.photos[0][-1].file_id)
            target = BytesIO()
            await bot.download_file(telegram_file.file_path, destination=target)
            data = target.getvalue()
            return base64.b64encode(data).decode() if len(data) <= 8_000_000 else ""
        except Exception:
            logger.exception("Unable to load guest bot avatar")
            return ""

    async def _questionnaire_chat_id(self) -> int:
        if self._binding is None:
            response = await self._api("GET", "/v1/bindings")
            self._binding = response.get("binding") or {}
        return int(self._binding.get("questionnaire_chat_id") or 0)

    @staticmethod
    def _card(message: Message, result: dict[str, Any]) -> str:
        sender = message.from_user
        caller = message.guest_bot_caller_user
        username = f"@{html.escape(sender.username)}" if sender and sender.username else "нет"
        caller_name = html.escape(caller.full_name) if caller else "неизвестен"
        caller_id = caller.id if caller else 0
        original = html.escape((message.text or message.caption or "")[:1800])
        return (
            "⛔ <b>Анкета: посторонний гостевой бот</b>\n\n"
            f"Бот: {html.escape(sender.full_name if sender else 'без имени')}\n"
            f"Username: {username}\n"
            f"ID: <code>{sender.id if sender else 0}</code>\n"
            f"Причина: {html.escape(str(result.get('reason') or 'гостевой бот'))}\n"
            f"Вызвал: {caller_name} (ID <code>{caller_id}</code>)\n"
            "Наказание: бот заблокирован\n\n"
            f"<b>Сообщение:</b>\n{original or 'без текста'}"
        )

    async def _send_card(
        self, bot: Bot, message: Message, result: dict[str, Any]
    ) -> None:
        chat_id = await self._questionnaire_chat_id()
        if not chat_id:
            return
        text = self._card(message, result)
        keyboard = incident_keyboard(str(result.get("incident_id") or ""))
        encoded = str(result.get("questionnaire_photo_base64") or "")
        if encoded:
            try:
                photo = BufferedInputFile(
                    base64.b64decode(encoded, validate=True),
                    filename=f"guest_bot_{message.from_user.id}.jpg",
                )
                if len(text) <= 1000:
                    await bot.send_photo(
                        chat_id=chat_id,
                        photo=photo,
                        caption=text,
                        parse_mode="HTML",
                        reply_markup=keyboard,
                        has_spoiler=True,
                    )
                    return
            except Exception:
                logger.exception("Unable to attach guest bot avatar")
        await bot.send_message(
            chat_id=chat_id,
            text=text,
            parse_mode="HTML",
            reply_markup=keyboard,
        )

    async def _process_guest_bot(self, bot: Bot, message: Message) -> None:
        sender = message.from_user
        caller = message.guest_bot_caller_user
        if not sender or not caller:
            return
        username = (sender.username or "").casefold()
        if (
            sender.id == bot.id
            or sender.id in self.trusted_bot_ids
            or username in self.trusted_bot_usernames
        ):
            return
        trigger_message_id = self._trigger_message_id(message)
        payload = {
            "event_type": "bot_message",
            "chat_id": message.chat.id,
            "chat_title": message.chat.title or "",
            "log_username": sender.username or "",
            "profile": {
                "user_id": sender.id,
                "first_name": sender.first_name or "",
                "last_name": sender.last_name or "",
                "bio": "",
                "avatar_base64": await self._avatar_base64(bot, sender.id),
                "avatar_ocr_text": "",
            },
            "message": {
                "message_id": message.message_id,
                "text": message.text or message.caption or "",
                "sender_is_bot": True,
                "guest_bot_caller_user_id": caller.id,
                "trigger_message_id": trigger_message_id,
            },
        }
        result = await self._api("POST", "/v1/check", payload)
        if not result.get("violation"):
            return
        try:
            await bot.ban_chat_member(
                chat_id=message.chat.id,
                user_id=sender.id,
                revoke_messages=True,
            )
        except Exception:
            logger.exception("Unable to ban guest bot")
        try:
            await bot.delete_message(message.chat.id, message.message_id)
        except Exception:
            logger.exception("Unable to delete guest bot message")
        if trigger_message_id and result.get("delete_trigger_message"):
            try:
                await bot.delete_message(message.chat.id, trigger_message_id)
            except Exception:
                logger.exception("Unable to delete guest bot trigger")
        await self._send_card(bot, message, result)

    async def __call__(self, handler, event: Message, data: dict[str, Any]):
        if not isinstance(event, Message):
            return await handler(event, data)
        self._remember_mentions(event)
        if event.from_user and event.from_user.is_bot and event.guest_bot_caller_user:
            await self._process_guest_bot(data["bot"], event)
            return None
        return await handler(event, data)

    async def _decision_callback(self, query: CallbackQuery, bot: Bot) -> None:
        if not query.data or not query.from_user:
            return
        _, decision, incident_id = query.data.split(":", 2)
        result = await self._api(
            "POST",
            f"/v1/incidents/{incident_id}/decision",
            {"decision": decision, "decided_by": query.from_user.id},
        )
        if decision == "skip":
            try:
                await bot.unban_chat_member(
                    chat_id=int(result["chat_id"]),
                    user_id=int(result["user_id"]),
                    only_if_banned=True,
                )
            except Exception:
                logger.exception("Unable to unban skipped guest bot")
        verdict = "пропущено, блокировка снята" if decision == "skip" else "заблокирован"
        actor = f"@{query.from_user.username}" if query.from_user.username else str(query.from_user.id)
        suffix = f"\n\nРешение: {verdict}\nВердикт вынес: {html.escape(actor)}"
        try:
            if query.message.photo:
                await query.message.edit_caption(
                    caption=(query.message.caption or "") + suffix,
                    parse_mode="HTML",
                    reply_markup=None,
                )
            else:
                await query.message.edit_text(
                    text=(query.message.text or "") + suffix,
                    parse_mode="HTML",
                    reply_markup=None,
                )
        finally:
            await query.answer("Готово")


def install_ihm_guest_bot_guard(
    dispatcher: Dispatcher,
    *,
    api_key: str,
    base_url: str = "https://ihm.thebeautifulmusic.ru",
    trusted_bot_ids: Iterable[int] = (),
    trusted_bot_usernames: Iterable[str] = (),
) -> IHMGuestBotGuard:
    """Attach the guard to an existing aiogram Dispatcher."""
    guard = IHMGuestBotGuard(
        api_key,
        base_url=base_url,
        trusted_bot_ids=trusted_bot_ids,
        trusted_bot_usernames=trusted_bot_usernames,
    )
    dispatcher.message.outer_middleware(guard)
    dispatcher.include_router(guard.router)
    return guard
