import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    Application,
    CommandHandler,
    CallbackQueryHandler,
    MessageHandler,
    ContextTypes,
    filters,
)

# =========================
# КОНВЕРТЕР
# =========================

class TeamCodeConverter:
    TEAM_CONVERSION_CHARS = "QWERTYUPASDFGHJKLZCVBNM23456789"
    TEAM_TAG = "X"

    def __init__(self):
        self.conversion_chars = self.TEAM_CONVERSION_CHARS
        self.code_suffix = self.TEAM_TAG

    def to_long(self, hi, lo):
        return ((hi & 0xFFFFFFFF) << 32) | (lo & 0xFFFFFFFF)

    def to_long_s(self, hi, lo):
        return (hi << 32) | lo

    def convert(self, value):
        tag = ""
        base = len(self.conversion_chars)

        while value > 0:
            char_index = value % base
            tag = self.conversion_chars[char_index] + tag
            value -= char_index
            value //= base

        return tag

    def to_code(self, high_int, low_int):
        if high_int < 256:
            l = self.to_long(low_int >> 24, high_int | (low_int << 8))
            res = self.convert(l)
            return self.code_suffix + res
        else:
            return None

    def to_id(self, code):
        if len(code) >= 14:
            return -1

        code_sub = code[1:]

        if len(code_sub) < 1:
            return self.to_long(0, 0)

        i = 0
        unk6 = 0
        unk7 = 0

        while i < len(code_sub):
            sub = code_sub[i]
            sub_idx = self.conversion_chars.find(sub)

            if sub_idx == -1:
                return self.to_long(-1, -1)

            unk12 = unk6 * len(self.conversion_chars) + sub_idx

            unk7 = (
                (self.to_long(int(unk7), unk6) * len(self.conversion_chars) + sub_idx)
                >> 32
            )

            unk6 = unk12
            i += 1

        if (unk12 & unk7) != -1:
            v13 = self.to_long_s(int(unk7), unk12) >> 8
            lo_int = int(v13 & 0x7FFFFFFF)
            hi_int = unk6 & 0xFF
            return self.to_long(hi_int, lo_int)

        return self.to_long(-1, -1)

    def id_to_code(self, player_id):
        hi = (player_id >> 32) & 0xFFFFFFFF
        lo = player_id & 0xFFFFFFFF
        return self.to_code(hi, lo)

    def code_to_id(self, code):
        return self.to_id(code)

    def is_valid_code(self, code: str) -> bool:
        if not code.startswith(self.code_suffix):
            return False

        if len(code) < 2 or len(code) > 13:
            return False

        for c in code[1:]:
            if c not in self.conversion_chars:
                return False

        decoded = self.code_to_id(code)

        if decoded == -1:
            return False

        reconstructed = self.id_to_code(decoded)
        return reconstructed == code

    def get_nearby_codes(self, code, before=0, after=0):
        base_id = self.code_to_id(code)

        if base_id == -1:
            return None

        results = []
        found = 0
        total_needed = before + after + 1

        i = max(0, base_id - before)

        while found < total_needed:
            generated_code = self.id_to_code(i)

            if generated_code and self.is_valid_code(generated_code):
                is_selected = generated_code == code
                results.append((generated_code, is_selected))
                found += 1

            i += 1

            if i > base_id + after + 10000:
                break

        return results

    def longest_common_run(self, a: str, b: str) -> int:
        """Самый длинный кусок подряд совпадающих букв между двумя тегами"""
        max_len = 0
        len_a, len_b = len(a), len(b)

        for i in range(len_a):
            for j in range(len_b):
                length = 0
                while (i + length < len_a and
                       j + length < len_b and
                       a[i + length] == b[j + length]):
                    length += 1
                if length > max_len:
                    max_len = length
        return max_len

    def find_blatnye(self, code: str, radius: int = 500, min_match: int = 4):
        """
        Ищет теги рядом, у которых много букв подряд совпадает с исходным.
        """
        base_id = self.code_to_id(code)
        if base_id == -1:
            return None

        results = []

        for offset in range(-radius, radius + 1):
            if offset == 0:
                continue

            check_id = base_id + offset
            if check_id < 0:
                continue

            generated = self.id_to_code(check_id)
            if generated and self.is_valid_code(generated):
                match_len = self.longest_common_run(code, generated)
                if match_len >= min_match:
                    results.append({
                        "code": generated,
                        "match": match_len,
                        "offset": offset
                    })

        # Сортируем: сначала самые похожие, потом ближайшие
        results.sort(key=lambda x: (-x["match"], abs(x["offset"])))
        return results


# =========================
# БОТ
# =========================

converter = TeamCodeConverter()
user_data = {}

logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    level=logging.INFO
)
logger = logging.getLogger(__name__)


def get_main_keyboard():
    keyboard = [
        [InlineKeyboardButton("🔢 Код → ID", callback_data="code_to_id")],
        [InlineKeyboardButton("🔠 ID → Код", callback_data="id_to_code")],
        [InlineKeyboardButton("📋 Соседние коды", callback_data="nearby")],
        [InlineKeyboardButton("💎 Блатные теги", callback_data="blatnye")],
    ]
    return InlineKeyboardMarkup(keyboard)


def get_direction_keyboard():
    keyboard = [
        [
            InlineKeyboardButton("← Назад", callback_data="dir_back"),
            InlineKeyboardButton("↔ Туда и туда", callback_data="dir_both"),
            InlineKeyboardButton("Вперёд →", callback_data="dir_forward"),
        ],
        [InlineKeyboardButton("◀️ Назад в меню", callback_data="back")]
    ]
    return InlineKeyboardMarkup(keyboard)


def get_back_keyboard():
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("◀️ Назад в меню", callback_data="back")]
    ])


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    user_data.pop(user_id, None)

    text = (
        "🎮 <b>Brawl Stars Team Code Converter</b>\n\n"
        "Выбери действие:"
    )
    await update.message.reply_text(
        text,
        reply_markup=get_main_keyboard(),
        parse_mode="HTML"
    )


async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()

    user_id = query.from_user.id
    data = query.data

    if data == "back":
        user_data.pop(user_id, None)
        await query.edit_message_text(
            "🎮 <b>Brawl Stars Team Code Converter от Weizy BS @weizybs</b>\n\nВыбери действие:",
            reply_markup=get_main_keyboard(),
            parse_mode="HTML"
        )
        return

    if data == "code_to_id":
        user_data[user_id] = {"state": "code_to_id"}
        await query.edit_message_text(
            "🔢 <b>Код → ID</b>\n\nОтправь код комнаты (например: <code>XWTAG</code>):",
            parse_mode="HTML",
            reply_markup=get_back_keyboard()
        )

    elif data == "id_to_code":
        user_data[user_id] = {"state": "id_to_code"}
        await query.edit_message_text(
            "🔠 <b>ID → Код</b>\n\nОтправь числовой ID:",
            parse_mode="HTML",
            reply_markup=get_back_keyboard()
        )

    elif data == "nearby":
        user_data[user_id] = {"state": "waiting_direction"}
        await query.edit_message_text(
            "📋 <b>Соседние коды</b>\n\nВ какую сторону искать?",
            reply_markup=get_direction_keyboard(),
            parse_mode="HTML"
        )

    elif data == "blatnye":
        user_data[user_id] = {"state": "waiting_blatnye_code"}
        await query.edit_message_text(
            "💎 <b>Блатные теги</b>\n\n"
            "Отправь свой код комнаты.\n"
            "Я найду рядом теги, у которых много букв подряд совпадает с твоим.",
            parse_mode="HTML",
            reply_markup=get_back_keyboard()
        )

    elif data in ("dir_back", "dir_both", "dir_forward"):
        direction_map = {
            "dir_back": "back",
            "dir_both": "both",
            "dir_forward": "forward"
        }
        direction_text = {
            "back": "← только назад",
            "both": "↔ в обе стороны",
            "forward": "только вперёд →"
        }

        direction = direction_map[data]
        user_data[user_id] = {
            "state": "waiting_amount",
            "direction": direction
        }

        await query.edit_message_text(
            f"📋 <b>Соседние коды</b>\n\n"
            f"Направление: <b>{direction_text[direction]}</b>\n\n"
            f"Введи количество комнат:",
            parse_mode="HTML",
            reply_markup=get_back_keyboard()
        )


async def message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    text = update.message.text.strip()

    data = user_data.get(user_id)

    if not data:
        await update.message.reply_text(
            "Выбери действие:",
            reply_markup=get_main_keyboard()
        )
        return

    state = data.get("state")

    # ---------- Код → ID ----------
    if state == "code_to_id":
        code = text.upper()
        result = converter.code_to_id(code)

        if result != -1:
            reply = f"✅ <b>Код:</b> <code>{code}</code>\n🔢 <b>ID:</b> <code>{result}</code>"
        else:
            reply = f"❌ Неверный код: <code>{code}</code>"

        await update.message.reply_text(reply, parse_mode="HTML", reply_markup=get_back_keyboard())
        user_data.pop(user_id, None)

    # ---------- ID → Код ----------
    elif state == "id_to_code":
        try:
            player_id = int(text)
            code = converter.id_to_code(player_id)

            if code:
                reply = f"✅ <b>ID:</b> <code>{player_id}</code>\n🔠 <b>Код:</b> <code>{code}</code>"
            else:
                reply = f"❌ Не удалось преобразовать ID: <code>{player_id}</code>"
        except ValueError:
            reply = "❌ Введи корректное число"

        await update.message.reply_text(reply, parse_mode="HTML", reply_markup=get_back_keyboard())
        user_data.pop(user_id, None)

    # ---------- Соседние: ждём количество ----------
    elif state == "waiting_amount":
        try:
            amount = int(text)
            if amount < 1 or amount > 200:
                await update.message.reply_text(
                    "❌ Введи число от 1 до 200",
                    reply_markup=get_back_keyboard()
                )
                return

            user_data[user_id]["amount"] = amount
            user_data[user_id]["state"] = "waiting_code"

            direction = data["direction"]
            direction_text = {
                "back": f"← {amount} назад",
                "both": f"↔ {amount} назад и {amount} вперёд",
                "forward": f"{amount} вперёд →"
            }

            await update.message.reply_text(
                f"📋 <b>Соседние коды</b>\n\n"
                f"Режим: <b>{direction_text[direction]}</b>\n\n"
                f"Теперь отправь код комнаты:",
                parse_mode="HTML",
                reply_markup=get_back_keyboard()
            )

        except ValueError:
            await update.message.reply_text(
                "❌ Введи целое число",
                reply_markup=get_back_keyboard()
            )

    # ---------- Соседние: ждём код ----------
    elif state == "waiting_code":
        code = text.upper()
        amount = data["amount"]
        direction = data["direction"]

        if direction == "back":
            before, after = amount, 0
        elif direction == "forward":
            before, after = 0, amount
        else:
            before, after = amount, amount

        results = converter.get_nearby_codes(code, before=before, after=after)

        if results is None:
            reply = f"❌ Неверный код: <code>{code}</code>"
        else:
            lines = [f"📋 <b>Соседние коды</b> для <code>{code}</code>:\n"]
            for gen_code, is_selected in results:
                if is_selected:
                    lines.append(f"<code>{gen_code}</code>  ← <b>твой код</b>")
                else:
                    lines.append(f"<code>{gen_code}</code>")
            reply = "\n".join(lines)

        await update.message.reply_text(reply, parse_mode="HTML", reply_markup=get_back_keyboard())
        user_data.pop(user_id, None)

    # ---------- Блатные теги ----------
    elif state == "waiting_blatnye_code":
        code = text.upper()
        results = converter.find_blatnye(code, radius=600, min_match=4)

        if results is None:
            reply = f"❌ Неверный код: <code>{code}</code>"
        elif not results:
            reply = f"💎 По тегу <code>{code}</code>\n\nНету блатных тегов рядом."
        else:
            lines = [f"💎 <b>Блатные теги</b> рядом с <code>{code}</code>:\n"]
            for item in results[:30]:
                offset = item["offset"]
                sign = f"+{offset}" if offset > 0 else str(offset)
                lines.append(
                    f"<code>{item['code']}</code>  "
                    f"(совпало {item['match']})  [{sign}]"
                )
            reply = "\n".join(lines)

        await update.message.reply_text(reply, parse_mode="HTML", reply_markup=get_back_keyboard())
        user_data.pop(user_id, None)


def main():
    TOKEN = "8695013449:AAHPgDcJUSGaLyA2VwqrombwzYwqpRhQKtk"

    app = Application.builder().token(TOKEN).build()

    app.add_handler(CommandHandler("start", start))
    app.add_handler(CallbackQueryHandler(button_handler))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, message_handler))

    print("Бот запущен...")
    app.run_polling(allowed_updates=Update.ALL_TYPES)


if __name__ == "__main__":
    main()