"""
captcha.py — логика обнаружения и решения капч при вступлении в чаты.
Независимый модуль — можно изменять алгоритм решения без затрагивания join.py.
"""
import asyncio
import re
import random
from typing import Optional, List
from urllib.parse import urlparse, parse_qs

from telethon import TelegramClient, events

from config import CAPTCHA_WINDOW_SEC, CAPTCHA_CLICK_PAUSE
from bot_ui.logger_setup import logger
from models import Account

CAPTCHA_TEXT_HINTS = [
    "captcha", "капча", "verify", "verification", "верификац",
    "i am human", "i\'m human", "im human", "я человек", "я не бот",
    "press the button", "нажмите кнопку", "подтвердите", "подтвердить", "не бот",
    "solve", "решите", "математическ", "math", "выберите", "click", "кликните",
    "security", "protect", "anti-spam", "антиспам", "антибот", "anti-bot",
    "human check", "robot check", "проверка", "not a bot",
]

# Известные капча-боты
CAPTCHA_BOT_USERNAMES = {
    "shaborobot", "shieldsbot", "grouphelpbot", "captchabot", "comaborobot",
    "combotcaptchabot", "verifbot", "joincheckbot", "groupguardbot",
    "miss_rose_bot", "grpbutler_bot", "lisabot",
}
CAPTCHA_BUTTON_HINTS = [
    "i am human", "i\'m human", "im human", "i am not a robot", "i\'m not a robot",
    "not a robot", "verify", "prove", "confirm", "i agree", "continue", "submit",
    "я человек", "я не бот", "подтверд", "я не робот", "я согласен",
    "не робот", "продолжить", "нажмите", "нажми", "подтвердить", "принять",
    "я людина", "підтверд",
]


def _has_captcha_keywords(text: str) -> bool:
    t = (text or "").lower()
    return any(k in t for k in CAPTCHA_TEXT_HINTS)


def is_captcha_bot(sender) -> bool:
    """Проверяет является ли отправитель известным капча-ботом."""
    if not sender:
        return False
    uname = (getattr(sender, "username", "") or "").lower()
    return uname in CAPTCHA_BOT_USERNAMES


def _parse_simple_math(text: str) -> Optional[int]:
    s = (text or "")
    s = (s.replace("×", "*").replace("÷", "/").replace("х", "x")
           .replace("плюс", "+").replace("минус", "-")
           .replace("умножить на", "*").replace("разделить на", "/")
           .replace("plus", "+").replace("minus", "-")
           .replace("times", "*").replace("divided by", "/"))
    m = re.search(r"(\d{1,4})\s*([+\-x*/])\s*(\d{1,4})", s)
    if not m:
        return None
    a, op, b = int(m.group(1)), m.group(2), int(m.group(3))
    if op in ("x", "*"):
        return a * b
    if op == "+":
        return a + b
    if op == "-":
        return a - b
    if op == "/" and b != 0 and a % b == 0:
        return a // b
    return None


async def _click_button_by_text(ev, preferred_texts: List[str], fallback_random: bool = False) -> bool:
    try:
        btns = ev.buttons or []
        flat = []
        for row in btns:
            for b in row:
                flat.append((b, getattr(b, "text", "") or ""))
        if preferred_texts:
            for b, txt in flat:
                lt = (txt or "").lower()
                if any(h in lt for h in preferred_texts):
                    await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
                    try:
                        await ev.message.click(text=txt)
                    except Exception:
                        idx = flat.index((b, txt))
                        await ev.message.click(idx)
                    return True
        if fallback_random and flat:
            await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
            chosen_idx = random.randint(0, len(flat) - 1)
            await ev.message.click(chosen_idx)
            return True
        return False
    except Exception as e:
        logger.debug("Failed to click button: %s", e)
        return False


async def _maybe_start_pm_from_url_button(acc: Account, ev) -> bool:
    try:
        if acc.client is None:
            return False
        if not ev.message or not ev.message.reply_markup:
            return False
        for row in ev.message.reply_markup.rows:
            for btn in getattr(row, "buttons", []):
                url = getattr(btn, "url", None)
                if not url:
                    continue
                try:
                    u = urlparse(url)
                except Exception:
                    continue
                if u.netloc.lower() in ("t.me", "telegram.me") and u.path.strip("/") and not u.path.startswith("/joinchat"):
                    bot_username = u.path.strip("/").split("/", 1)[0]
                    start = parse_qs(u.query or "").get("start", [""])[0]
                    msg = "/start " + start if start else "/start"
                    await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
                    await acc.client.send_message(bot_username, msg)
                    logger.info("CAPTCHA: отправил %r в PM @%s", msg, bot_username)
                    return True
        return False
    except Exception as e:
        logger.debug("PM-start from URL button failed: %s", e)
        return False


def _extract_digits_from_text(text: str) -> Optional[str]:
    """Извлекает цифровой код из текста капчи (например 'Введите код: 4829')."""
    patterns = [
        r'(?:код|code|number|цифр|введите|enter|type|напишите)[:\s]*(\d{3,6})',
        r'(\d{4,6})',  # fallback — просто 4-6 цифр подряд
    ]
    for p in patterns:
        m = re.search(p, text, re.IGNORECASE)
        if m:
            return m.group(1)
    return None


async def _try_click_digit_buttons(ev, target_digits: str) -> bool:
    """Нажимает кнопки с цифрами в правильном порядке (инлайн-клавиатура)."""
    if not ev.buttons:
        return False
    # Собираем все кнопки с одной цифрой
    digit_buttons = {}
    for row in ev.buttons:
        for b in row:
            txt = (getattr(b, "text", "") or "").strip()
            if len(txt) == 1 and txt.isdigit():
                digit_buttons[txt] = b

    if not digit_buttons:
        return False

    # Нажимаем кнопки в порядке цифр
    for digit in target_digits:
        if digit in digit_buttons:
            await asyncio.sleep(random.uniform(0.5, 1.2))
            try:
                await ev.message.click(text=digit)
            except Exception:
                return False
        else:
            return False
    return True


async def try_solve_captcha_event(acc: Account, ev) -> Optional[str]:
    if acc.client is None:
        return None
    text = ev.raw_text or ""
    has_kw = _has_captcha_keywords(text)
    has_buttons = bool(ev.buttons)
    math_ans = _parse_simple_math(text)
    has_media = bool(getattr(ev, 'photo', None) or getattr(ev, 'media', None))

    if has_buttons:
        # 1. Кнопка-подтверждение (Я не бот, Verify и т.д.)
        if await _click_button_by_text(ev, CAPTCHA_BUTTON_HINTS, fallback_random=False):
            return "clicked:hint"

        # 2. Математический ответ — нажать кнопку с числом
        if math_ans is not None:
            for row in ev.buttons or []:
                for b in row:
                    if str(math_ans) == (getattr(b, "text", "") or "").strip():
                        await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
                        try:
                            await ev.message.click(text=str(math_ans))
                        except Exception:
                            pass
                        return "clicked:math"
            # Попробовать написать ответ
            await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
            try:
                await acc.client.send_message(ev.chat_id, str(math_ans), reply_to=ev.id)
                return "answered:math"
            except Exception:
                pass

        # 3. Цифровой код из текста — набрать через инлайн-кнопки
        digit_code = _extract_digits_from_text(text)
        if digit_code:
            if await _try_click_digit_buttons(ev, digit_code):
                return "clicked:digits"
            # Или написать код сообщением
            await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
            try:
                await acc.client.send_message(ev.chat_id, digit_code, reply_to=ev.id)
                return "answered:digits"
            except Exception:
                pass

        # 4. PM-бот через URL-кнопку
        if await _maybe_start_pm_from_url_button(acc, ev):
            return "pm_start"

        # 5. Fallback — нажать случайную кнопку если есть ключевые слова/медиа
        if has_media or has_kw:
            if await _click_button_by_text(ev, [], fallback_random=True):
                return "clicked:random"

    # Без кнопок — попробовать ответить текстом
    if math_ans is not None:
        await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
        try:
            await acc.client.send_message(ev.chat_id, str(math_ans), reply_to=ev.id)
            return "answered:math"
        except Exception:
            pass

    digit_code = _extract_digits_from_text(text)
    if digit_code:
        await asyncio.sleep(random.uniform(*CAPTCHA_CLICK_PAUSE))
        try:
            await acc.client.send_message(ev.chat_id, digit_code, reply_to=ev.id)
            return "answered:digits"
        except Exception:
            pass

    return None


async def wait_for_event_compat(client: TelegramClient, builder, timeout: float):
    if hasattr(client, "wait_for_event"):
        return await client.wait_for_event(builder, timeout=timeout)
    loop = asyncio.get_event_loop()
    fut: asyncio.Future = loop.create_future()
    async def handler(ev):
        if not fut.done():
            fut.set_result(ev)
    client.add_event_handler(handler, builder)
    try:
        return await asyncio.wait_for(fut, timeout=timeout)
    finally:
        try:
            client.remove_event_handler(handler, builder)
        except Exception:
            pass


async def watch_and_solve_captcha(acc: Account, chat_id: int, window_sec: int = CAPTCHA_WINDOW_SEC) -> str:
    if acc.client is None:
        return "not_seen"
    try:
        if not await acc.client.is_user_authorized():
            return "not_seen"
    except Exception:
        return "not_seen"
    deadline = asyncio.get_event_loop().time() + window_sec
    builder = events.NewMessage(chats=chat_id)
    solved = False
    while True:
        timeout = deadline - asyncio.get_event_loop().time()
        if timeout <= 0:
            break
        try:
            ev = await wait_for_event_compat(acc.client, builder, timeout=timeout)
        except asyncio.TimeoutError:
            break
        try:
            # Проверяем — от капча-бота ли сообщение
            sender = await ev.get_sender() if hasattr(ev, 'get_sender') else None
            if sender and is_captcha_bot(sender):
                logger.info("[captcha] Обнаружен капча-бот @%s в чате %s",
                            getattr(sender, "username", "?"), chat_id)
            action = await try_solve_captcha_event(acc, ev)
            if action:
                logger.info("[captcha] Решено: %s (чат %s)", action, chat_id)
                solved = True
                break
        except Exception as e:
            logger.debug("[captcha] Ошибка решения: %s", e)
            continue
    return "solved" if solved else "not_seen"
