# -*- coding: utf-8 -*-
"""
پنل مدیریت کامل و حرفه‌ای ربات
=========================
همه چیز از اینجا توسط ادمین قابل تغییره:
- قیمت‌ها و ضرایب کوین
- شماره کارت بانکی
- کانال‌های اجباری (Force Join)
- کانال لاگ خریدها
- تایید/رد پرداخت‌های در انتظار
- ارسال پیام همگانی
- مدیریت ادمین‌ها و کاربرها (جستجو، هدیه/کسر کوین، مسدودسازی)
- تیکت‌های پشتیبانی
- 🧩 مدیریت کامل دکمه‌های منو: افزودن دکمه‌ی سفارشی جدید، تغییر نام هر
  دکمه، فعال/غیرفعال‌سازی (حذف نمایشی)، جابه‌جایی ترتیب، و حذف کامل
  دکمه‌های سفارشی
- ✏️ مدیریت کامل تمام متن‌های ربات (خوش‌آمدگویی، راهنما، کیف پول،
  پیام‌های تبلیغ کانال، فاکتور خرید و ده‌ها متن دیگه) به تفکیک دسته
"""

import html
import time
from datetime import datetime

import telebot
from telebot import types

import database as db
import keyboards as kb
import texts as texts_registry
import utils


def register(bot: telebot.TeleBot):

    # -----------------------------------------------------------------
    # ورود به پنل
    # -----------------------------------------------------------------
    @bot.message_handler(commands=["admin"])
    @bot.message_handler(func=lambda m: m.text == "🛠 پنل مدیریت")
    def admin_panel(message):
        if not db.is_admin(message.from_user.id):
            bot.send_message(message.chat.id, "⛔️ دسترسی نداری.")
            return
        bot.send_message(message.chat.id, "🛠 پنل مدیریت ربات", reply_markup=kb.admin_main_kb())

    @bot.callback_query_handler(func=lambda c: c.data == "adm_back")
    def adm_back(call):
        if not db.is_admin(call.from_user.id):
            return
        utils.clear_state(call.from_user.id)
        try:
            bot.edit_message_text(
                "🛠 پنل مدیریت ربات",
                call.message.chat.id,
                call.message.message_id,
                reply_markup=kb.admin_main_kb(),
            )
        except Exception:
            bot.send_message(call.message.chat.id, "🛠 پنل مدیریت ربات", reply_markup=kb.admin_main_kb())

    def _require_admin(call):
        if not db.is_admin(call.from_user.id):
            bot.answer_callback_query(call.id, "⛔️ دسترسی نداری.", show_alert=True)
            return False
        return True

    def _safe_edit(chat_id, message_id, text, reply_markup=None, parse_mode=None):
        """
        مثل bot.edit_message_text ولی اگه ویرایش به هر دلیلی شکست بخوره
        (مثلا محتوا عین قبله و تلگرام خطای "message not modified" میده،
        یا پیام خیلی قدیمیه)، به‌جاش یه پیام جدید میفرسته - تا ادمین
        هیچ‌وقت با زدن یه دکمه، بدون هیچ واکنشی نمونه.
        """
        try:
            bot.edit_message_text(text, chat_id, message_id, reply_markup=reply_markup, parse_mode=parse_mode)
        except Exception:
            try:
                bot.send_message(chat_id, text, reply_markup=reply_markup, parse_mode=parse_mode)
            except Exception:
                pass

    def _safe_edit_markup(chat_id, message_id, reply_markup=None):
        """مثل edit_message_reply_markup ولی اگه شکست خورد، بی‌سروصدا رد میشه."""
        try:
            bot.edit_message_reply_markup(chat_id, message_id, reply_markup=reply_markup)
        except Exception:
            pass

    def _send_with_retry(send_fn, max_retries=2):
        """
        برای ارسال‌های انبوه (پیام همگانی، اطلاع‌رسانی شارژ گروهی و ...):
        اگه تلگرام بگه داری خیلی سریع پیام میفرستی (flood control / کد ۴۲۹)،
        طبق زمانی که خودش اعلام میکنه صبر میکنه و دوباره امتحان میکنه -
        مهم برای وقتی که تعداد کاربرها به چند هزار نفر برسه.
        """
        for attempt in range(max_retries + 1):
            try:
                send_fn()
                return True
            except Exception as e:
                retry_after = None
                try:
                    retry_after = e.result_json.get("parameters", {}).get("retry_after")
                except Exception:
                    pass
                if retry_after and attempt < max_retries:
                    time.sleep(retry_after + 1)
                    continue
                return False
        return False

    # -----------------------------------------------------------------
    # آمار
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_stats")
    def adm_stats(call):
        if not _require_admin(call):
            return
        s = db.get_stats()
        text = (
            "📊 آمار ربات\n\n"
            f"👥 تعداد کاربران: {utils.fmt_num(s['users'])}\n"
            f"💰 مجموع کوین‌های در گردش: {utils.fmt_num(s['total_coins'])}\n"
            f"📢 کمپین‌های فعال: {utils.fmt_num(s['active_campaigns'])}\n"
            f"📦 مجموع کمپین‌ها: {utils.fmt_num(s['total_campaigns'])}\n"
            f"🤝 مجموع جوین‌های ثبت‌شده: {utils.fmt_num(s['total_joins'])}\n"
            f"💵 مجموع درآمد تاییدشده: {utils.toman(s['total_income'])}\n"
            f"⏳ پرداخت‌های در انتظار: {utils.fmt_num(s['pending_payments'])}\n"
            f"🎫 تیکت‌های باز: {utils.fmt_num(s['open_tickets'])}"
        )
        _safe_edit(call.message.chat.id, call.message.message_id, text, reply_markup=kb.admin_back_kb())

    # -----------------------------------------------------------------
    # تنظیمات قیمت‌ها
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_prices")
    def adm_prices(call):
        if not _require_admin(call):
            return
        _safe_edit(
            call.message.chat.id,
            call.message.message_id,
            "⚙️ تنظیمات قیمت‌ها و کوین\nروی هر مورد بزن تا مقدارش رو تغییر بدی:",
            reply_markup=kb.prices_kb(),
        )

    _PRICE_FIELDS = {
        "set_coin_per_join": ("coin_per_join", "جایزه عضویت (تعداد کوین به‌ازای هر جوین موفق) رو بفرست - مثلا 1 یا 2:"),
        "set_coins_unit": ("coins_unit", "واحد کوین برای قیمت‌گذاری رو بفرست (مثلا 10 یعنی هر ۱۰ کوین):"),
        "set_price_per_unit": ("price_per_unit", "قیمت هر واحد کوین رو به تومان بفرست (فقط عدد):"),
        "set_min_purchase": ("min_purchase_coins", "حداقل تعداد کوین قابل خرید رو بفرست:"),
        "set_referral_bonus": ("referral_bonus", "تعداد کوین جایزه‌ی هر زیرمجموعه رو بفرست:"),
        "set_daily_bonus": ("daily_bonus", "تعداد کوین پاداش روزانه رو بفرست:"),
    }

    @bot.callback_query_handler(func=lambda c: c.data in _PRICE_FIELDS)
    def ask_price_field(call):
        if not _require_admin(call):
            return
        key, prompt = _PRICE_FIELDS[call.data]
        utils.set_state(call.from_user.id, "awaiting_setting_value", {"key": key})
        bot.answer_callback_query(call.id)
        bot.send_message(call.message.chat.id, prompt, reply_markup=kb.cancel_kb())

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_setting_value"
    )
    def receive_setting_value(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        key = state["data"]["key"]
        value = message.text.strip()
        if not value.lstrip("-").isdigit():
            bot.send_message(message.chat.id, "فقط عدد قبول میشه. دوباره بفرست:")
            return
        db.set_setting(key, value)
        utils.clear_state(admin_id)
        bot.send_message(
            message.chat.id, "✅ ذخیره شد.", reply_markup=kb.main_menu(admin_id)
        )
        bot.send_message(message.chat.id, "⚙️ تنظیمات قیمت‌ها:", reply_markup=kb.prices_kb())

    # -----------------------------------------------------------------
    # کارت بانکی
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_card")
    def adm_card(call):
        if not _require_admin(call):
            return
        card = db.get_setting("card_number")
        holder = db.get_setting("card_holder") or "تنظیم نشده"
        card_display = f"<code>{html.escape(card)}</code>" if card else "تنظیم نشده"
        text = f"💳 شماره کارت فعلی: {card_display}\n👤 نام صاحب کارت: {html.escape(holder)}"
        kb_ = types.InlineKeyboardMarkup(row_width=1)
        kb_.add(
            types.InlineKeyboardButton("✏️ تغییر شماره کارت", callback_data="set_card_number"),
            types.InlineKeyboardButton("✏️ تغییر نام صاحب کارت", callback_data="set_card_holder"),
            types.InlineKeyboardButton("🔙 بازگشت", callback_data="adm_back"),
        )
        _safe_edit(call.message.chat.id, call.message.message_id, text, reply_markup=kb_, parse_mode="HTML")

    @bot.callback_query_handler(func=lambda c: c.data in ("set_card_number", "set_card_holder"))
    def ask_card_value(call):
        if not _require_admin(call):
            return
        key = "card_number" if call.data == "set_card_number" else "card_holder"
        prompt = "شماره کارت جدید رو بفرست:" if key == "card_number" else "نام صاحب کارت رو بفرست:"
        utils.set_state(call.from_user.id, "awaiting_card_value", {"key": key})
        bot.answer_callback_query(call.id)
        bot.send_message(call.message.chat.id, prompt, reply_markup=kb.cancel_kb())

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_card_value"
    )
    def receive_card_value(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        key = state["data"]["key"]
        db.set_setting(key, message.text.strip())
        utils.clear_state(admin_id)
        bot.send_message(message.chat.id, "✅ ذخیره شد.", reply_markup=kb.main_menu(admin_id))

    # -----------------------------------------------------------------
    # کانال‌های اجباری
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_force")
    def adm_force(call):
        if not _require_admin(call):
            return
        _safe_edit(
            call.message.chat.id,
            call.message.message_id,
            "📢 کانال‌های اجباری (کاربر قبل از استفاده از ربات باید عضو این‌ها بشه):",
            reply_markup=kb.force_channels_kb(),
        )

    @bot.callback_query_handler(func=lambda c: c.data == "add_force")
    def ask_add_force(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_force_channel")
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id,
            "آیدی کانال رو بفرست (مثلا @channel).\n"
            "⚠️ ربات باید توی اون کانال ادمین باشه.",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_force_channel"
    )
    def receive_force_channel(message):
        admin_id = message.from_user.id
        channel_id = utils.normalize_channel_id(message.text)
        try:
            chat = bot.get_chat(channel_id)
            title = chat.title or channel_id
        except Exception:
            title = channel_id
            chat = None

        channel_link = ""
        if chat is not None:
            channel_link = chat.invite_link or ""
            if not channel_link:
                try:
                    channel_link = bot.export_chat_invite_link(channel_id)
                except Exception:
                    channel_link = ""

        if not channel_link and not channel_id.startswith("@"):
            bot.send_message(
                message.chat.id,
                "⚠️ نتونستم لینک دعوت این کانال رو بسازم. مطمئن شو به ربات دسترسی "
                "«دعوت کاربران با لینک» رو هم توی تنظیمات ادمین کانال داده باشی، "
                "بعد دوباره امتحان کن.",
            )
            return

        db.add_force_channel(channel_id, title, channel_link)
        utils.clear_state(admin_id)
        bot.send_message(message.chat.id, f"✅ کانال {title} اضافه شد.", reply_markup=kb.main_menu(admin_id))

    @bot.callback_query_handler(func=lambda c: c.data.startswith("rm_force_"))
    def remove_force(call):
        if not _require_admin(call):
            return
        fid = int(call.data.rsplit("_", 1)[1])
        for ch in db.list_force_channels():
            if ch["id"] == fid:
                db.remove_force_channel(ch["channel_id"])
                break
        bot.answer_callback_query(call.id, "حذف شد.")
        _safe_edit_markup(call.message.chat.id, call.message.message_id, reply_markup=kb.force_channels_kb())

    # -----------------------------------------------------------------
    # کانال لاگ خریدها
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_logch")
    def adm_logch(call):
        if not _require_admin(call):
            return
        current = db.get_setting("log_channel_id") or "تنظیم نشده"
        text = (
            f"📥 کانال فعلی برای اطلاع‌رسانی خریدها: {current}\n\n"
            "این کانالی هست که با هر خرید/کمپین جدید، خبرش با دکمه‌ی ورود پست میشه."
        )
        kb_ = types.InlineKeyboardMarkup()
        kb_.add(types.InlineKeyboardButton("✏️ تغییر کانال", callback_data="set_log_channel"))
        kb_.add(types.InlineKeyboardButton("🔙 بازگشت", callback_data="adm_back"))
        _safe_edit(call.message.chat.id, call.message.message_id, text, reply_markup=kb_)

    @bot.callback_query_handler(func=lambda c: c.data == "set_log_channel")
    def ask_log_channel(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_log_channel")
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id,
            "آیدی کانال لاگ رو بفرست (مثلا @mychannel).\n"
            "⚠️ ربات باید توی اون کانال ادمین باشه تا بتونه پست کنه.",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_log_channel"
    )
    def receive_log_channel(message):
        admin_id = message.from_user.id
        channel_id = utils.normalize_channel_id(message.text)
        try:
            bot.send_message(channel_id, "✅ این کانال به عنوان کانال لاگ خریدها تنظیم شد.")
        except Exception:
            bot.send_message(
                message.chat.id,
                "نتونستم توی این کانال پیام بفرستم. مطمئن شو ربات ادمینه.",
            )
            return
        db.set_setting("log_channel_id", channel_id)
        utils.clear_state(admin_id)
        bot.send_message(message.chat.id, "✅ ذخیره شد.", reply_markup=kb.main_menu(admin_id))

    # -----------------------------------------------------------------
    # پرداخت‌های در انتظار
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_payments")
    def adm_payments(call):
        if not _require_admin(call):
            return
        rows = db.list_pending_payments()
        if not rows:
            _safe_edit(
                call.message.chat.id,
                call.message.message_id,
                "پرداخت در انتظاری وجود نداره ✅",
                reply_markup=kb.admin_back_kb(),
            )
            return
        _safe_edit(
            call.message.chat.id,
            call.message.message_id,
            f"⏳ {len(rows)} پرداخت در انتظار تایید هست. برای هرکدوم عکس رسید جدا ارسال میشه:",
            reply_markup=kb.admin_back_kb(),
        )
        for p in rows:
            buyer = db.get_user(p["user_id"])
            first_name = html.escape(buyer["first_name"] or "-") if buyer else "-"
            username = f"@{buyer['username']}" if buyer and buyer["username"] else "-"
            caption = (
                f"💳 درخواست خرید کوین #{p['id']}!\n\n"
                f"👤 کاربر : {first_name} | {html.escape(username)}\n"
                f"🆔️ آیدی عددی : <code>{p['user_id']}</code>\n"
                f"تعداد کوین: {utils.fmt_num(p['coins'])}\n"
                f"مبلغ: {utils.toman(p['amount_toman'])}"
            )
            try:
                bot.send_photo(
                    call.message.chat.id,
                    p["receipt_file"],
                    caption=caption,
                    parse_mode="HTML",
                    reply_markup=kb.payment_review_kb(p["id"]),
                )
            except Exception:
                bot.send_message(call.message.chat.id, caption, parse_mode="HTML", reply_markup=kb.payment_review_kb(p["id"]))

    # -----------------------------------------------------------------
    # ارسال همگانی - منوی گسترده (پیام ساده / دکمه‌ی لینک‌دار / شارژ و کسر همگانی)
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_broadcast")
    def adm_broadcast_menu(call):
        if not _require_admin(call):
            return
        text = (
            "📣 ارسال همگانی و عملیات گروهی\n\n"
            "📝 پیام ساده: متن یا عکس رو دقیقاً برای همه فوروارد میکنه.\n"
            "🔗 پیام با دکمه: میتونی یه دکمه با لینک دلخواه (مثلا لینک کانال یا سایت) بهش اضافه کنی.\n"
            "💰/➖ شارژ یا کسر همگانی: به همه‌ی کاربرها هم‌زمان کوین اضافه یا کم میکنه.\n\n"
            "یکی رو انتخاب کن:"
        )
        try:
            bot.edit_message_text(text, call.message.chat.id, call.message.message_id, reply_markup=kb.broadcast_menu_kb())
        except Exception:
            bot.send_message(call.message.chat.id, text, reply_markup=kb.broadcast_menu_kb())

    # ---- پیام ساده (فوروارد دقیق به همه، متن یا عکس) ----
    @bot.callback_query_handler(func=lambda c: c.data == "bc_simple")
    def ask_broadcast_simple(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_broadcast_simple")
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id,
            "پیامی که میخوای برای همه ارسال بشه رو بفرست (متن، عکس، ویدیو، فایل یا صوت):",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        content_types=["text", "photo", "video", "document", "audio", "animation", "voice", "video_note"],
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_broadcast_simple",
    )
    def receive_broadcast_simple_content(message):
        admin_id = message.from_user.id
        utils.set_state(
            admin_id, "confirm_broadcast_simple",
            {"chat_id": message.chat.id, "message_id": message.message_id},
        )
        try:
            bot.copy_message(message.chat.id, message.chat.id, message.message_id)
        except Exception:
            pass
        bot.send_message(
            message.chat.id,
            "☝️ این پیام برای همه ارسال بشه؟",
            reply_markup=kb.confirm_broadcast_kb("simple"),
        )

    @bot.callback_query_handler(func=lambda c: c.data == "bcconfirm_simple_yes")
    def confirm_broadcast_simple(call):
        if not _require_admin(call):
            return
        admin_id = call.from_user.id
        state = utils.get_state(admin_id)
        if not state or state["state"] != "confirm_broadcast_simple":
            bot.answer_callback_query(call.id, "این درخواست منقضی شده.", show_alert=True)
            return
        chat_id = state["data"]["chat_id"]
        message_id = state["data"]["message_id"]
        utils.clear_state(admin_id)
        bot.answer_callback_query(call.id, "در حال ارسال...")
        _run_broadcast_copy(chat_id, message_id, admin_id)

    @bot.callback_query_handler(func=lambda c: c.data == "bcconfirm_simple_no")
    def cancel_broadcast_simple(call):
        if not _require_admin(call):
            return
        utils.clear_state(call.from_user.id)
        bot.answer_callback_query(call.id, "لغو شد.")
        bot.send_message(call.message.chat.id, "لغو شد.", reply_markup=kb.main_menu(call.from_user.id))

    def _run_broadcast_copy(chat_id, message_id, admin_id):
        user_ids = db.all_user_ids()
        bot.send_message(chat_id, f"⏳ در حال ارسال به {len(user_ids)} کاربر...")
        sent, failed = 0, 0
        for uid in user_ids:
            ok = _send_with_retry(lambda uid=uid: bot.copy_message(uid, chat_id, message_id))
            if ok:
                sent += 1
            else:
                failed += 1
            time.sleep(0.05)  # جلوگیری از محدودیت نرخ ارسال تلگرام
        bot.send_message(
            chat_id,
            f"✅ پیام همگانی ارسال شد.\nموفق: {sent} | ناموفق: {failed}",
            reply_markup=kb.main_menu(admin_id),
        )

    # ---- پیام با دکمه‌ی لینک‌دار ----
    @bot.callback_query_handler(func=lambda c: c.data == "bc_button")
    def ask_broadcast_button_content(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_broadcast_button_content")
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id,
            "پیامی که میخوای با دکمه ارسال بشه رو بفرست (متن یا عکس با کپشن):",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        content_types=["text", "photo"],
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_broadcast_button_content",
    )
    def receive_broadcast_button_content(message):
        admin_id = message.from_user.id
        if message.content_type == "photo":
            content = {"type": "photo", "file_id": message.photo[-1].file_id, "caption": message.caption or ""}
        else:
            content = {"type": "text", "text": message.text}
        utils.set_state(admin_id, "awaiting_broadcast_button_label", {"content": content})
        bot.send_message(message.chat.id, "متن دکمه رو بفرست (مثلا «عضویت در کانال»):", reply_markup=kb.cancel_kb())

    @bot.message_handler(
        content_types=["video", "document", "audio", "animation", "voice", "video_note", "sticker", "location", "contact"],
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_broadcast_button_content",
    )
    def receive_broadcast_button_content_wrong_type(message):
        bot.send_message(
            message.chat.id,
            "برای پیام با دکمه فقط متن یا عکس پشتیبانی میشه. برای ویدیو/فایل از «📝 پیام ساده» استفاده کن.",
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_broadcast_button_label"
    )
    def receive_broadcast_button_label(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        utils.set_state(
            admin_id, "awaiting_broadcast_button_url",
            {"content": state["data"]["content"], "label": message.text.strip()},
        )
        bot.send_message(
            message.chat.id,
            "لینک دکمه رو بفرست (باید با http:// یا https:// یا t.me/ شروع بشه):",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_broadcast_button_url"
    )
    def receive_broadcast_button_url(message):
        admin_id = message.from_user.id
        url = message.text.strip()
        if not (url.startswith("http://") or url.startswith("https://") or url.startswith("t.me/")):
            bot.send_message(message.chat.id, "لینک معتبر نیست. باید با http:// یا https:// یا t.me/ شروع بشه:")
            return
        if url.startswith("t.me/"):
            url = "https://" + url

        state = utils.get_state(admin_id)
        content = state["data"]["content"]
        label = state["data"]["label"]
        utils.set_state(admin_id, "confirm_broadcast_button", {"content": content, "label": label, "url": url})

        markup = types.InlineKeyboardMarkup()
        markup.add(types.InlineKeyboardButton(label, url=url))
        try:
            if content["type"] == "photo":
                bot.send_photo(message.chat.id, content["file_id"], caption=content["caption"], reply_markup=markup)
            else:
                bot.send_message(message.chat.id, content["text"], reply_markup=markup)
        except Exception:
            pass
        bot.send_message(
            message.chat.id,
            "☝️ این پیام (با همین دکمه) برای همه ارسال بشه؟",
            reply_markup=kb.confirm_broadcast_kb("button"),
        )

    @bot.callback_query_handler(func=lambda c: c.data == "bcconfirm_button_yes")
    def confirm_broadcast_button(call):
        if not _require_admin(call):
            return
        admin_id = call.from_user.id
        state = utils.get_state(admin_id)
        if not state or state["state"] != "confirm_broadcast_button":
            bot.answer_callback_query(call.id, "این درخواست منقضی شده.", show_alert=True)
            return
        content = state["data"]["content"]
        label = state["data"]["label"]
        url = state["data"]["url"]
        utils.clear_state(admin_id)
        bot.answer_callback_query(call.id, "در حال ارسال...")

        markup = types.InlineKeyboardMarkup()
        markup.add(types.InlineKeyboardButton(label, url=url))

        user_ids = db.all_user_ids()
        bot.send_message(call.message.chat.id, f"⏳ در حال ارسال به {len(user_ids)} کاربر...")
        sent, failed = 0, 0
        for uid in user_ids:
            if content["type"] == "photo":
                ok = _send_with_retry(
                    lambda uid=uid: bot.send_photo(uid, content["file_id"], caption=content["caption"], reply_markup=markup)
                )
            else:
                ok = _send_with_retry(lambda uid=uid: bot.send_message(uid, content["text"], reply_markup=markup))
            if ok:
                sent += 1
            else:
                failed += 1
            time.sleep(0.05)
        bot.send_message(
            call.message.chat.id,
            f"✅ پیام همگانی (با دکمه) ارسال شد.\nموفق: {sent} | ناموفق: {failed}",
            reply_markup=kb.main_menu(admin_id),
        )

    @bot.callback_query_handler(func=lambda c: c.data == "bcconfirm_button_no")
    def cancel_broadcast_button(call):
        if not _require_admin(call):
            return
        utils.clear_state(call.from_user.id)
        bot.answer_callback_query(call.id, "لغو شد.")
        bot.send_message(call.message.chat.id, "لغو شد.", reply_markup=kb.main_menu(call.from_user.id))

    # ---- شارژ / کسر کوین همگانی ----
    @bot.callback_query_handler(func=lambda c: c.data in ("bc_addcoins", "bc_subcoins"))
    def ask_bulk_coins(call):
        if not _require_admin(call):
            return
        kind = "add" if call.data == "bc_addcoins" else "sub"
        utils.set_state(call.from_user.id, "awaiting_bulk_coin_amount", {"kind": kind})
        bot.answer_callback_query(call.id)
        prompt = "چند کوین به همه اضافه بشه؟" if kind == "add" else "چند کوین از همه کسر بشه؟"
        bot.send_message(call.message.chat.id, prompt, reply_markup=kb.cancel_kb())

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_bulk_coin_amount"
    )
    def receive_bulk_coin_amount(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        text = message.text.strip()
        if not text.isdigit() or int(text) <= 0:
            bot.send_message(message.chat.id, "فقط یه عدد بزرگ‌تر از صفر بفرست:")
            return
        amount = int(text)
        kind = state["data"]["kind"]
        user_count = db.user_count()
        verb = "اضافه" if kind == "add" else "کسر"
        # وضعیت رو به "منتظر تاییده" تغییر میدیم (نه پاک) تا موقع تایید نهایی
        # بدونیم دقیقاً باید چیکار کنیم؛ کلید تایید هم فوراً بعد از زدن پاک میشه
        utils.set_state(admin_id, "confirm_bulk_coins", {"kind": kind, "amount": amount})
        bot.send_message(
            message.chat.id,
            f"⚠️ مطمئنی میخوای {utils.fmt_num(amount)} کوین از/به حساب همه‌ی {utils.fmt_num(user_count)} "
            f"کاربر {verb} کنی؟ این عملیات فوریه و قابل بازگشت نیست.",
            reply_markup=kb.confirm_bulk_coins_kb(),
        )

    @bot.callback_query_handler(func=lambda c: c.data == "bc_confirm_yes")
    def confirm_bulk_coins(call):
        if not _require_admin(call):
            return
        admin_id = call.from_user.id
        state = utils.get_state(admin_id)
        if not state or state["state"] != "confirm_bulk_coins":
            bot.answer_callback_query(call.id, "این درخواست منقضی شده.", show_alert=True)
            return
        kind = state["data"]["kind"]
        amount = state["data"]["amount"]
        # فوراً پاک میکنیم - وگرنه دابل‌کلیک سریع ممکنه این عملیات رو
        # روی همه‌ی کاربرها دوبار اجرا کنه (دوبار شارژ یا دوبار کسر)
        utils.clear_state(admin_id)

        bot.answer_callback_query(call.id, "در حال اجرا...")

        if kind == "add":
            affected = db.bulk_add_coins(amount)
        else:
            affected = db.bulk_deduct_coins(amount)

        user_ids = db.all_user_ids()
        for uid in user_ids:
            new_balance = db.get_coins(uid)
            if kind == "add":
                text = (
                    f"🎁 تعداد {utils.fmt_num(amount)} کوین از طرف مدیریت به تمام کاربران اضافه شد.\n"
                    f"موجودی جدید شما: {utils.fmt_num(new_balance)}"
                )
            else:
                text = (
                    f"⚠️ تعداد {utils.fmt_num(amount)} کوین از طرف مدیریت از تمام کاربران کسر شد.\n"
                    f"موجودی جدید شما: {utils.fmt_num(new_balance)}"
                )
            _send_with_retry(lambda uid=uid, text=text: bot.send_message(uid, text))
            time.sleep(0.03)

        bot.send_message(
            call.message.chat.id,
            f"✅ عملیات روی {utils.fmt_num(affected)} کاربر اجرا شد.",
            reply_markup=kb.main_menu(admin_id),
        )

    @bot.callback_query_handler(func=lambda c: c.data == "bc_confirm_no")
    def cancel_bulk_coins(call):
        if not _require_admin(call):
            return
        utils.clear_state(call.from_user.id)
        bot.answer_callback_query(call.id, "لغو شد.")
        bot.send_message(call.message.chat.id, "لغو شد.", reply_markup=kb.main_menu(call.from_user.id))

    # -----------------------------------------------------------------
    # مدیریت ادمین‌ها
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_admins")
    def adm_admins(call):
        if not _require_admin(call):
            return
        _safe_edit(call.message.chat.id, call.message.message_id, "👤 لیست ادمین‌های ربات:", reply_markup=kb.admins_kb())

    @bot.callback_query_handler(func=lambda c: c.data == "add_admin")
    def ask_add_admin(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_new_admin")
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id, "آیدی عددی ادمین جدید رو بفرست:", reply_markup=kb.cancel_kb()
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_new_admin"
    )
    def receive_new_admin(message):
        admin_id = message.from_user.id
        text = message.text.strip()
        if not text.isdigit():
            bot.send_message(message.chat.id, "فقط آیدی عددی قبول میشه.")
            return
        db.add_admin(int(text))
        utils.clear_state(admin_id)
        bot.send_message(message.chat.id, "✅ ادمین جدید اضافه شد.", reply_markup=kb.main_menu(admin_id))

    @bot.callback_query_handler(func=lambda c: c.data.startswith("rm_admin_"))
    def remove_admin_cb(call):
        if not _require_admin(call):
            return
        target = int(call.data.rsplit("_", 1)[1])
        import config

        if target == config.OWNER_ID:
            bot.answer_callback_query(call.id, "نمیشه ادمین اصلی رو حذف کرد!", show_alert=True)
            return
        db.remove_admin(target)
        bot.answer_callback_query(call.id, "حذف شد.")
        _safe_edit_markup(call.message.chat.id, call.message.message_id, reply_markup=kb.admins_kb())

    # -----------------------------------------------------------------
    # مدیریت کاربر (جستجو، هدیه/کسر/تنظیم کوین، مسدودسازی، پیام مستقیم، کمپین‌ها)
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_finduser")
    def ask_find_user(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_find_user")
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id,
            "آیدی عددی، یوزرنیم یا اسم کاربر مورد نظر رو بفرست:\n\n"
            "💡 برای مدیریت حساب خودت هم می‌تونی آیدی عددی خودت رو بفرستی.",
            reply_markup=kb.cancel_kb(),
        )

    def _send_user_detail(chat_id, user, message_id=None):
        username = f"@{user['username']}" if user["username"] else "-"
        status = "⛔️ مسدود" if user["is_banned"] else "✅ فعال"
        name = html.escape(user["first_name"] or "-")
        joined = datetime.fromtimestamp(user["joined_at"]).strftime("%Y-%m-%d") if user["joined_at"] else "-"
        referrer = db.get_referrer(user["user_id"])
        referrer_text = (
            f"@{referrer['username']}" if referrer and referrer["username"]
            else (str(referrer["user_id"]) if referrer else "-")
        )
        campaigns = db.list_campaigns_by_owner(user["user_id"])
        admin_badge = " 👑 (ادمین)" if db.is_admin(user["user_id"]) else ""
        text = (
            f"👤 اطلاعات کاربر{admin_badge}\n\n"
            f"نام: {name}\n"
            f"یوزرنیم: {username}\n"
            f"آیدی عددی: <code>{user['user_id']}</code>\n"
            f"تاریخ عضویت: {joined}\n"
            f"موجودی: {utils.fmt_num(user['coins'])} کوین\n"
            f"وضعیت: {status}\n"
            f"تعداد جوین موفق: {utils.fmt_num(db.get_join_count(user['user_id']))}\n"
            f"تعداد زیرمجموعه: {utils.fmt_num(db.get_referral_count(user['user_id']))}\n"
            f"دعوت‌شده توسط: {referrer_text}\n"
            f"تعداد کمپین‌های ثبت‌شده: {utils.fmt_num(len(campaigns))}"
        )
        markup = kb.user_manage_kb(user["user_id"], bool(user["is_banned"]))
        if message_id:
            try:
                bot.edit_message_text(text, chat_id, message_id, reply_markup=markup, parse_mode="HTML")
                return
            except Exception:
                pass
        bot.send_message(chat_id, text, reply_markup=markup, parse_mode="HTML")

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_find_user"
    )
    def receive_find_user(message):
        admin_id = message.from_user.id
        utils.clear_state(admin_id)
        user = db.find_user(message.text)
        if not user:
            bot.send_message(message.chat.id, "کاربری پیدا نشد.", reply_markup=kb.main_menu(admin_id))
            return
        _send_user_detail(message.chat.id, user)

    @bot.callback_query_handler(
        func=lambda c: c.data.startswith("u_add_")
        or c.data.startswith("u_sub_")
        or c.data.startswith("u_set_")
        or c.data.startswith("u_ban_")
        or c.data.startswith("u_unban_")
        or c.data.startswith("u_msg_")
        or c.data.startswith("u_camps_")
    )
    def user_manage_action(call):
        if not _require_admin(call):
            return
        action, target_id = call.data.rsplit("_", 1)
        target_id = int(target_id)

        if action == "u_ban":
            if target_id == call.from_user.id:
                bot.answer_callback_query(call.id, "نمیتونی خودت رو مسدود کنی!", show_alert=True)
                return
            db.ban_user(target_id, 1)
            bot.answer_callback_query(call.id, "کاربر مسدود شد.")
            try:
                bot.send_message(target_id, "⛔️ دسترسی شما به ربات توسط ادمین مسدود شد.")
            except Exception:
                pass
        elif action == "u_unban":
            db.ban_user(target_id, 0)
            bot.answer_callback_query(call.id, "مسدودیت برداشته شد.")
            try:
                bot.send_message(target_id, "✅ دسترسی شما به ربات دوباره فعال شد.")
            except Exception:
                pass
        elif action in ("u_add", "u_sub", "u_set"):
            utils.set_state(
                call.from_user.id,
                "awaiting_user_coin_amount",
                {"target_id": target_id, "action": action},
            )
            bot.answer_callback_query(call.id)
            prompts = {
                "u_add": "چند کوین اضافه بشه؟",
                "u_sub": "چند کوین کسر بشه؟",
                "u_set": "موجودی دقیقاً چند کوین بشه؟",
            }
            bot.send_message(call.message.chat.id, prompts[action], reply_markup=kb.cancel_kb())
            return
        elif action == "u_msg":
            utils.set_state(call.from_user.id, "awaiting_direct_message", {"target_id": target_id})
            bot.answer_callback_query(call.id)
            bot.send_message(
                call.message.chat.id,
                "متن پیامی که میخوای مستقیم برای این کاربر بفرستی رو بنویس:",
                reply_markup=kb.cancel_kb(),
            )
            return
        elif action == "u_camps":
            bot.answer_callback_query(call.id)
            rows = db.list_campaigns_by_owner(target_id)
            if not rows:
                bot.send_message(call.message.chat.id, "این کاربر هیچ کمپینی نساخته.")
                return
            status_fa = {"active": "🟢 فعال", "finished": "✅ تکمیل‌شده", "stopped": "⛔️ متوقف‌شده"}
            lines = [f"📋 کمپین‌های کاربر <code>{target_id}</code>:\n"]
            for c in rows:
                done = c["total_coins"] - c["remaining"]
                title = html.escape(c["channel_title"] or "")
                lines.append(
                    f"#{c['id']} {title} — {status_fa.get(c['status'], c['status'])} "
                    f"({utils.fmt_num(done)}/{utils.fmt_num(c['total_coins'])})"
                )
            bot.send_message(call.message.chat.id, "\n".join(lines), parse_mode="HTML")
            return

        try:
            user = db.get_user(target_id)
            _send_user_detail(call.message.chat.id, user, message_id=call.message.message_id)
        except Exception:
            pass

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_user_coin_amount"
    )
    def receive_user_coin_amount(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        text = message.text.strip()
        if not text.isdigit():
            bot.send_message(message.chat.id, "فقط عدد بفرست:")
            return
        amount = int(text)
        target_id = state["data"]["target_id"]
        action = state["data"]["action"]
        utils.clear_state(admin_id)

        if action == "u_add":
            db.add_coins(target_id, amount)
            bot.send_message(
                message.chat.id, f"✅ {amount} کوین به حساب کاربر اضافه شد.", reply_markup=kb.main_menu(admin_id)
            )
            try:
                bot.send_message(target_id, f"🎁 ادمین {amount} کوین به حسابت اضافه کرد!")
            except Exception:
                pass
        elif action == "u_sub":
            db.force_deduct_coins(target_id, amount)
            bot.send_message(
                message.chat.id, f"✅ {amount} کوین از حساب کاربر کسر شد.", reply_markup=kb.main_menu(admin_id)
            )
            try:
                bot.send_message(target_id, f"⚠️ ادمین {amount} کوین از حسابت کسر کرد.")
            except Exception:
                pass
        else:  # u_set
            db.set_coins(target_id, amount)
            bot.send_message(
                message.chat.id, f"✅ موجودی کاربر روی {amount} کوین تنظیم شد.", reply_markup=kb.main_menu(admin_id)
            )
            try:
                bot.send_message(target_id, f"ℹ️ موجودی حسابت توسط ادمین روی {amount} کوین تنظیم شد.")
            except Exception:
                pass

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_direct_message"
    )
    def receive_direct_message(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        target_id = state["data"]["target_id"]
        utils.clear_state(admin_id)
        try:
            bot.send_message(target_id, f"📩 پیام از ادمین:\n\n{message.text}")
            bot.send_message(message.chat.id, "✅ پیام ارسال شد.", reply_markup=kb.main_menu(admin_id))
        except Exception:
            bot.send_message(
                message.chat.id,
                "نتونستم برای این کاربر پیام بفرستم (شاید ربات رو بلاک کرده).",
                reply_markup=kb.main_menu(admin_id),
            )

    # -----------------------------------------------------------------
    # تیکت‌های پشتیبانی
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_tickets")
    def adm_tickets(call):
        if not _require_admin(call):
            return
        rows = db.list_open_tickets()
        if not rows:
            _safe_edit(
                call.message.chat.id,
                call.message.message_id,
                "تیکت باز وجود نداره ✅",
                reply_markup=kb.admin_back_kb(),
            )
            return
        _safe_edit(
            call.message.chat.id,
            call.message.message_id,
            f"🎫 {len(rows)} تیکت باز هست:",
            reply_markup=kb.admin_back_kb(),
        )
        for t in rows:
            ticket_user = db.get_user(t["user_id"])
            first_name = html.escape(ticket_user["first_name"] or "-") if ticket_user else "-"
            username = f"@{ticket_user['username']}" if ticket_user and ticket_user["username"] else "-"
            bot.send_message(
                call.message.chat.id,
                f"🎫 تیکت #{t['id']}\n\n"
                f"👤 کاربر : {first_name} | {html.escape(username)}\n"
                f"🆔️ آیدی عددی : <code>{t['user_id']}</code>\n\n"
                f"{html.escape(t['message'])}",
                parse_mode="HTML",
                reply_markup=kb.ticket_reply_kb(t["id"], t["user_id"]),
            )

    @bot.callback_query_handler(func=lambda c: c.data.startswith("tk_reply_"))
    def ask_ticket_reply(call):
        if not _require_admin(call):
            return
        _, _, ticket_id, target_user = call.data.split("_")
        utils.set_state(
            call.from_user.id,
            "awaiting_ticket_reply",
            {"ticket_id": int(ticket_id), "target_user": int(target_user)},
        )
        bot.answer_callback_query(call.id)
        bot.send_message(call.message.chat.id, "پاسخت رو بنویس:", reply_markup=kb.cancel_kb())

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_ticket_reply"
    )
    def receive_ticket_reply(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        ticket_id = state["data"]["ticket_id"]
        target_user = state["data"]["target_user"]
        utils.clear_state(admin_id)
        db.close_ticket(ticket_id)
        try:
            bot.send_message(target_user, f"📞 پاسخ پشتیبانی:\n\n{message.text}")
            bot.send_message(message.chat.id, "✅ پاسخ ارسال شد.", reply_markup=kb.main_menu(admin_id))
        except Exception:
            bot.send_message(
                message.chat.id, "نتونستم برای کاربر پیام بفرستم (شاید ربات رو بلاک کرده).",
                reply_markup=kb.main_menu(admin_id),
            )

    @bot.callback_query_handler(func=lambda c: c.data.startswith("tk_close_"))
    def close_ticket_cb(call):
        if not _require_admin(call):
            return
        ticket_id = int(call.data.rsplit("_", 1)[1])
        db.close_ticket(ticket_id)
        bot.answer_callback_query(call.id, "تیکت بسته شد.")

    # -----------------------------------------------------------------
    # مدیریت دکمه‌های منو (افزودن، حذف، تغییر نام، جابه‌جایی)
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_buttons")
    def adm_buttons(call):
        if not _require_admin(call):
            return
        text = (
            "🧩 مدیریت دکمه‌های منو\n\n"
            "✅ = فعال و نمایش داده میشه | 🚫 = مخفیه | 🆕 = دکمه‌ی سفارشی توی\n\n"
            "روی هر دکمه بزن تا ویرایشش کنی، یا یه دکمه‌ی کاملا جدید بساز:"
        )
        try:
            bot.edit_message_text(text, call.message.chat.id, call.message.message_id, reply_markup=kb.buttons_list_kb())
        except Exception:
            bot.send_message(call.message.chat.id, text, reply_markup=kb.buttons_list_kb())

    @bot.callback_query_handler(func=lambda c: c.data.startswith("btnview_"))
    def btn_view(call):
        if not _require_admin(call):
            return
        bid = int(call.data.rsplit("_", 1)[1])
        btn = db.get_menu_button_by_id(bid)
        if not btn:
            bot.answer_callback_query(call.id, "پیدا نشد.", show_alert=True)
            return
        lines = [f"برچسب فعلی: {btn['label']}", f"وضعیت: {'✅ فعال' if btn['enabled'] else '🚫 مخفی'}"]
        if btn["kind"] == "custom":
            lines.append(f"متن پاسخ:\n{btn['custom_text'] or '—'}")
        text = "\n".join(lines)
        try:
            bot.edit_message_text(text, call.message.chat.id, call.message.message_id, reply_markup=kb.button_detail_kb(btn))
        except Exception:
            bot.send_message(call.message.chat.id, text, reply_markup=kb.button_detail_kb(btn))

    @bot.callback_query_handler(func=lambda c: c.data.startswith("btnrename_"))
    def btn_ask_rename(call):
        if not _require_admin(call):
            return
        bid = int(call.data.rsplit("_", 1)[1])
        utils.set_state(call.from_user.id, "awaiting_button_rename", {"button_id": bid})
        bot.answer_callback_query(call.id)
        bot.send_message(call.message.chat.id, "برچسب جدید دکمه رو بفرست (میتونی از ایموجی هم استفاده کنی):", reply_markup=kb.cancel_kb())

    _RESERVED_LABELS = {"❌ انصراف", "🛠 پنل مدیریت"}

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_button_rename"
    )
    def receive_button_rename(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        bid = state["data"]["button_id"]
        new_label = message.text.strip()
        utils.clear_state(admin_id)

        if new_label in _RESERVED_LABELS:
            bot.send_message(
                message.chat.id,
                "این برچسب برای عملکردهای داخلی ربات رزرو شده و قابل استفاده نیست.",
                reply_markup=kb.main_menu(admin_id),
            )
            return
        if db.label_taken(new_label, exclude_id=bid):
            bot.send_message(
                message.chat.id,
                "این برچسب قبلا برای یه دکمه‌ی دیگه استفاده شده. یه اسم دیگه انتخاب کن.",
                reply_markup=kb.main_menu(admin_id),
            )
            return

        db.rename_menu_button(bid, new_label)
        bot.send_message(message.chat.id, "✅ برچسب دکمه تغییر کرد.", reply_markup=kb.main_menu(admin_id))

    @bot.callback_query_handler(func=lambda c: c.data.startswith("btntoggle_"))
    def btn_toggle(call):
        if not _require_admin(call):
            return
        bid = int(call.data.rsplit("_", 1)[1])
        btn = db.get_menu_button_by_id(bid)
        if not btn:
            bot.answer_callback_query(call.id, "پیدا نشد.", show_alert=True)
            return
        db.set_menu_button_enabled(bid, not btn["enabled"])
        bot.answer_callback_query(call.id, "انجام شد.")
        btn = db.get_menu_button_by_id(bid)
        try:
            bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=kb.button_detail_kb(btn))
        except Exception:
            pass

    @bot.callback_query_handler(func=lambda c: c.data.startswith("btnup_") or c.data.startswith("btndown_"))
    def btn_move(call):
        if not _require_admin(call):
            return
        direction = -1 if call.data.startswith("btnup_") else 1
        bid = int(call.data.rsplit("_", 1)[1])
        db.move_menu_button(bid, direction)
        bot.answer_callback_query(call.id, "جابه‌جا شد.")
        btn = db.get_menu_button_by_id(bid)
        try:
            bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=kb.button_detail_kb(btn))
        except Exception:
            pass

    @bot.callback_query_handler(func=lambda c: c.data.startswith("btndelete_"))
    def btn_delete(call):
        if not _require_admin(call):
            return
        bid = int(call.data.rsplit("_", 1)[1])
        db.delete_menu_button(bid)
        bot.answer_callback_query(call.id, "دکمه حذف شد.")
        try:
            bot.edit_message_text(
                "🧩 مدیریت دکمه‌های منو:", call.message.chat.id, call.message.message_id, reply_markup=kb.buttons_list_kb()
            )
        except Exception:
            pass

    @bot.callback_query_handler(func=lambda c: c.data.startswith("btnedittext_"))
    def btn_ask_edit_text(call):
        if not _require_admin(call):
            return
        bid = int(call.data.rsplit("_", 1)[1])
        utils.set_state(call.from_user.id, "awaiting_button_custom_text", {"button_id": bid})
        bot.answer_callback_query(call.id)
        bot.send_message(call.message.chat.id, "متنی که با زدن این دکمه نشون داده بشه رو بفرست:", reply_markup=kb.cancel_kb())

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_button_custom_text"
    )
    def receive_button_custom_text(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        bid = state["data"]["button_id"]
        utils.clear_state(admin_id)
        db.update_custom_button_text(bid, message.text)
        bot.send_message(message.chat.id, "✅ متن دکمه به‌روز شد.", reply_markup=kb.main_menu(admin_id))

    @bot.callback_query_handler(func=lambda c: c.data == "btn_add_custom")
    def btn_add_custom_ask_label(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_new_button_label")
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id,
            "برچسب دکمه‌ی جدید رو بفرست (مثلا «📌 قوانین ربات»):",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_new_button_label"
    )
    def receive_new_button_label(message):
        admin_id = message.from_user.id
        label = message.text.strip()
        if label in _RESERVED_LABELS:
            bot.send_message(message.chat.id, "این برچسب رزرو شده. یه اسم دیگه بفرست:")
            return
        if db.label_taken(label):
            bot.send_message(message.chat.id, "این برچسب قبلا استفاده شده. یه اسم دیگه بفرست:")
            return
        utils.set_state(admin_id, "awaiting_new_button_text", {"label": label})
        bot.send_message(
            message.chat.id,
            "حالا متنی که با زدن این دکمه به کاربر نشون داده بشه رو بفرست:",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_new_button_text"
    )
    def receive_new_button_text(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        label = state["data"]["label"]
        utils.clear_state(admin_id)
        db.add_custom_button(label, message.text)
        bot.send_message(
            message.chat.id, f"✅ دکمه‌ی «{label}» اضافه شد و همین الان توی منو فعاله.",
            reply_markup=kb.main_menu(admin_id),
        )

    # -----------------------------------------------------------------
    # مدیریت متن‌های ربات (همه‌ی پیام‌های کاربرمحور از اینجا قابل ویرایشن)
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_texts")
    def adm_texts(call):
        if not _require_admin(call):
            return
        text = "✏️ مدیریت متن‌های ربات\n\nیه دسته انتخاب کن، بعد متنی که میخوای عوض کنی رو بزن:"
        try:
            bot.edit_message_text(text, call.message.chat.id, call.message.message_id, reply_markup=kb.text_categories_kb())
        except Exception:
            bot.send_message(call.message.chat.id, text, reply_markup=kb.text_categories_kb())

    @bot.callback_query_handler(func=lambda c: c.data.startswith("txtcat_"))
    def adm_texts_category(call):
        if not _require_admin(call):
            return
        category = call.data.split("_", 1)[1]
        cat_label = texts_registry.CATEGORIES.get(category, category)
        text = f"{cat_label}\n\nمتن‌هایی که با ✏️ مشخص شدن قبلا سفارشی شدن؛ بقیه هنوز پیش‌فرضن."
        try:
            bot.edit_message_text(text, call.message.chat.id, call.message.message_id, reply_markup=kb.texts_in_category_kb(category))
        except Exception:
            bot.send_message(call.message.chat.id, text, reply_markup=kb.texts_in_category_kb(category))

    @bot.callback_query_handler(func=lambda c: c.data.startswith("txtedit_"))
    def adm_text_edit_ask(call):
        if not _require_admin(call):
            return
        key = call.data.split("_", 1)[1]
        current = db.get_text(key)
        entry = texts_registry.TEXT_DEFAULTS.get(key)
        label = entry[1] if entry else key
        utils.set_state(call.from_user.id, "awaiting_text_value", {"key": key})
        bot.answer_callback_query(call.id)
        bot.send_message(
            call.message.chat.id,
            f"✏️ {label}\n\nمتن فعلی:\n\n{current}\n\n————————\n"
            "متن جدید رو بفرست. اگه توی متن پرانتزهای {مثل این} دیدی، دست بهشون نزن چون "
            "با اطلاعات واقعی (مثل قیمت یا تعداد) جایگزین میشن.",
            reply_markup=kb.cancel_kb(),
        )

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_text_value"
    )
    def receive_text_value(message):
        admin_id = message.from_user.id
        state = utils.get_state(admin_id)
        key = state["data"]["key"]
        utils.clear_state(admin_id)
        db.set_text(key, message.text)
        bot.send_message(message.chat.id, "✅ متن به‌روز شد.", reply_markup=kb.main_menu(admin_id))

    # -----------------------------------------------------------------
    # آیدی پشتیبانی
    # -----------------------------------------------------------------
    @bot.callback_query_handler(func=lambda c: c.data == "adm_support")
    def adm_support(call):
        if not _require_admin(call):
            return
        current = db.get_setting("support_username") or "تنظیم نشده"
        text = f"📞 آیدی پشتیبانی فعلی: {current}\n\nاین آیدی به کاربرها برای ارتباط مستقیم نشون داده میشه."
        kb_ = types.InlineKeyboardMarkup()
        kb_.add(types.InlineKeyboardButton("✏️ تغییر", callback_data="set_support"))
        kb_.add(types.InlineKeyboardButton("🔙 بازگشت", callback_data="adm_back"))
        _safe_edit(call.message.chat.id, call.message.message_id, text, reply_markup=kb_)

    @bot.callback_query_handler(func=lambda c: c.data == "set_support")
    def ask_support(call):
        if not _require_admin(call):
            return
        utils.set_state(call.from_user.id, "awaiting_support_username")
        bot.answer_callback_query(call.id)
        bot.send_message(call.message.chat.id, "آیدی پشتیبانی رو بفرست (مثلا @support):", reply_markup=kb.cancel_kb())

    @bot.message_handler(
        func=lambda m: db.is_admin(m.from_user.id)
        and utils.get_state(m.from_user.id)
        and utils.get_state(m.from_user.id)["state"] == "awaiting_support_username"
    )
    def receive_support_username(message):
        admin_id = message.from_user.id
        db.set_setting("support_username", message.text.strip())
        utils.clear_state(admin_id)
        bot.send_message(message.chat.id, "✅ ذخیره شد.", reply_markup=kb.main_menu(admin_id))
