# -*- coding: utf-8 -*-
"""
ربات فروش کانفیگ WireGuard با پرداخت کارت‌به‌کارت.

فلوی کار:
1) کاربر /start می‌زند -> لیست پلن‌ها را می‌بیند.
2) کاربر پلن را انتخاب می‌کند -> ربات شماره کارت را نشان می‌دهد و منتظر رسید می‌ماند.
3) کاربر عکس رسید را می‌فرستد -> ربات آن را به ادمین فوروارد می‌کند با دو دکمه: تایید / رد.
4) ادمین «تایید» را می‌زند -> ربات از ادمین می‌خواهد فایل کانفیگ را برای همان کاربر بفرستد.
5) ادمین فایل .conf را می‌فرستد -> ربات آن را مستقیماً برای خریدار ارسال می‌کند.
6) ادمین می‌تواند «رد» بزند تا به کاربر اطلاع داده شود.

مدیریت پلن‌ها با دستورات متنی توسط ادمین انجام می‌شود:
/addplan نام | قیمت | توضیح
/plans
/delplan شناسه
"""

import datetime
import gzip
import hmac
import html
import json
import logging
import math
import os
import re
import sqlite3
import tempfile
import threading
import time
import traceback

import requests
from flask import Flask, request, jsonify

import config
import database as db
import telegram_api as tg

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("wg_bot")

app = Flask(__name__)
db.init_db()

# مهلت اعتبار قیمت لحظه‌ای برای سفارش‌های یوسی که با کارت‌به‌کارت پرداخت می‌شوند (ثانیه)
UC_ORDER_EXPIRY_SECONDS = 10 * 60

# مهلت پرداخت سفارش‌های پلن و شارژ کیف پول (ثانیه): اگر تا این مدت رسیدی نفرستاده باشند،
# بی‌صدا «لغو شده» می‌شوند و در «سفارش‌های من» هم نمایش داده نمی‌شوند.
UNPAID_ORDER_EXPIRY_SECONDS = 60 * 60

# سفارش‌های «لغو شده»ی بدون رسید (هرگز پولی جابه‌جا نشده) بعد از این مدت از دیتابیس پاک می‌شوند (ثانیه)
CANCELLED_ORDER_RETENTION_SECONDS = 30 * 24 * 60 * 60

# کارهای پس‌زمینه (انقضا/پاکسازی/یادآوری) حداکثر هر این‌قدر ثانیه یک‌بار اجرا می‌شوند تا
# روی هر پیام کاربر دیتابیس بی‌دلیل کوئری نخورد
MAINTENANCE_INTERVAL_SECONDS = 60

# فاصله‌ی زمانی که قبل از پایان اشتراک، یادآوری تمدید فرستاده می‌شود (ثانیه)
RENEWAL_REMINDER_WINDOW_SECONDS = 2 * 24 * 60 * 60  # ۲ روز

# حداقل فاصله‌ی مجاز بین دو کلیک روی دکمه‌های شیشه‌ای برای هر کاربر (ثانیه) - ضد اسپم
CALLBACK_RATE_LIMIT_SECONDS = 0.6

# بیشترین تعداد مجاز برای هر آیتم یوسی در یک سفارش
UC_MAX_QUANTITY = 10

# دکمه‌های سریع انتخاب تعداد یوسی
UC_QUICK_QUANTITIES = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

# تیکت پشتیبانی: اگر پشتیبانی جواب داده باشد و کاربر این مدت هیچ پیامی نفرستد، تیکت خودکار بسته می‌شود
# (۲ روز؛ برای یک روز عدد 24 * 60 * 60 بگذارید)
TICKET_AUTO_CLOSE_SECONDS = 2 * 24 * 60 * 60

# سبد خرید یوسی که بیش از این مدت دست‌نخورده بماند، خودکار پاک می‌شود (ثانیه)
UC_CART_RETENTION_SECONDS = 7 * 24 * 60 * 60

# آیدی عددی پابجی: فقط رقم، کامل (حداقل ۸ رقم). ارقام فارسی/عربی قبل از بررسی به انگلیسی تبدیل می‌شوند.
PUBG_ID_RE = re.compile(r"(?<!\d)\d{8,14}(?!\d)")
_DIGIT_TRANS = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")


# ---------------------------------------------------------------------------
# ایموجی‌های اختصاصی (پریمیوم) تلگرام
#
# نکته‌ی مهم فنی: از نسخه‌ی Bot API 9.4 به بعد، تلگرام فیلد "icon_custom_emoji_id" را
# به دکمه‌های inline keyboard اضافه کرده که یک ایموجی پریمیوم را قبل از متن دکمه نشان
# می‌دهد (مثل استفاده‌ای که در PREMIUM_EMOJI["card"/"check"/"cross"][1] پایین‌تر شده است).
# محدودیت مهم: طبق داکیومنت رسمی تلگرام، این ایموجی روی دکمه فقط زمانی واقعاً نمایش
# داده می‌شود که صاحب ربات (همان اکانتی که ربات را ساخته، نه لزوماً کاربری که پیام را
# می‌بیند) یک اشتراک Telegram Premium فعال داشته باشد؛ یا این‌که برای ربات از طریق
# Fragment یوزرنیم اضافه خریده باشید. اگر هیچ‌کدام از این دو شرط برقرار نباشد، تلگرام
# احتمالاً همین فیلد را نادیده می‌گیرد و فقط متن دکمه (بدون آیکون) نمایش داده می‌شود.
# ---------------------------------------------------------------------------
PREMIUM_EMOJI = {
    "refresh":   ("🔄", "5433878454078556670"),  # آپدیت قیمت
    "card":      ("💳", "5445353829304387411"),  # کارت به کارت
    "wallet":    ("👛", "5242296412728741790"),  # کیف پول
    "check":     ("✅", "5469891741970605890"),  # تیک سبز
    "cross":     ("❌", "5469806645783571411"),  # ضربدر قرمز
    "discount":  ("🏷", "5470112352965764439"),  # کد تخفیف دارم
    "duration":  ("🗓", "5274055917766202507"),  # مدت اشتراک
    "contact":   ("📞", "5465169893580086142"),  # تماس با ما
    "uc":        ("🎯", "5341695390615880298"),  # خرید یوسی
    "gaming":    ("🎮", "5861561131226632101"),  # اشتراک گیمینگ وایرگارد
    "back":      ("🔙", "5462931610028510371"),  # بازگشت
    "home":      ("🏠", "5465226866321268133"),  # منو اصلی
    "shop":      ("🛍", "5312361253610475399"),  # فروشگاه
    "pending":   ("🧾", "5810003553531467794"),  # سفارش‌های در انتظار
    "admin":     ("🛠", "5341715473882955310"),  # مدیریت‌های پنل ادمین
    "plus":      ("➕", "5393194986252542669"),  # به‌جای همه‌ی +ها
    "minus":     ("➖", "5382261056078881010"),  # به‌جای همه‌ی -ها (کم کردن تعداد)
    "broadcast": ("📢", "5472037524811555705"),  # پیام همگانی
    "invite":    ("🎁", "5199749070830197566"),  # کسب درآمد
    "usdt":      ("💵", "5470060860602856810"),  # نرخ لحظه‌ای تتر
    "history":   ("🧾", "5256110612395605858"),  # تاریخچه تراکنش‌ها
    "notify":    ("🔔", "5472405023688237378"),  # خبرم کن وقتی فروش شروع شد
    # ایموجی‌های بخش احراز هویت (عمداً در نگاشت خودکار set_premium_map نیستند، فقط همین‌جا استفاده می‌شوند)
    "num1":      ("1️⃣", "5461033346152804686"),  # عدد یک
    "num2":      ("2️⃣", "5469622950032321924"),  # عدد دو
    "forbid":    ("⛔", "5915838346638138046"),  # علامت ممنوع
    "pricelist": ("📋", "5962854524698760122"),  # لیست قیمت یوسی
    "controller": ("🎮", "4972415571384599106"),  # کنترلر (هرجا 🎮 در متن بود)
    "clock":     ("⏰", "6030821505984630931"),  # ساعت / انقضای زمان
    "warning":   ("⚠", "5469940983770657399"),  # اخطار (⚠️)
    "users":     ("👥", "5958460691550572213"),  # تعداد کاربر
    "stats":     ("📊", "5244837092042750681"),  # آمار / گزارش
    "ticket":    ("🎫", "5958416857114350105"),  # تیکت
    "lock":      ("🔒", "5296369303661067030"),  # قفل (پایان گفتگو، بستن تیکت)
    "disable":   ("🚫", "5469985299243214923"),  # غیرفعال کن / هرجا 🚫 بود
    "trash":     ("🗑", "5445267414562389170"),  # حذف / هرجا سطل زباله بود
    "edit":      ("✏", "5395444784611480792"),  # ویرایش / هرجا مداد بود
    "info":      ("ℹ", "5445331903496332384"),  # اطلاع‌رسانی (مثلاً «سفارش توسط ادمین دیگری تایید شد»)
    "msg":       ("💬", "5472239203590888751"),  # پیام داخل تیکت‌ها
    "reply":     ("↩", "5834924817798665219"),  # پاسخ (دکمه‌ها و متن‌ها)
    # آموزش اتصال (عمداً در نگاشت خودکار set_premium_map نیستند)
    "wait":      ("⏳", "6071317996704370355"),  # ایموجی انیمیشنی «منتظر بمانید» (تکی و بزرگ بعد از رسید)
    "tutorial":  ("📚", "5884511941807838166"),  # آموزش اتصال
    "android":   ("🤖", "5071174135288169449"),  # Android
    "ios":       ("🍏", "5764873381509863886"),  # iOS
    "windows":   ("🖥", "5933550168996581523"),  # Windows
}


# نگاشت ایموجی معمولی -> ایموجی پریمیوم؛ به‌صورت خودکار روی متن همه‌ی پیام‌ها و کپشن‌ها
# (از جمله پیام‌های ادمین) اعمال می‌شود. 🎮 عمداً نیست چون در پیام‌ها هم برای پابجی
# و هم برای اشتراک گیمینگ استفاده می‌شود؛ 🎮 ساده‌ی داخل متن، ایموجی کنترلر می‌گیرد و
# جاهایی که «اشتراک گیمینگ» است با ("e", "gaming") دستی مشخص شده.
tg.set_premium_map({
    PREMIUM_EMOJI[k][0]: PREMIUM_EMOJI[k][1]
    for k in ("refresh", "card", "wallet", "check", "cross", "discount", "duration", "contact",
              "uc", "back", "home", "shop", "pending", "admin", "plus", "broadcast", "invite",
              "controller", "clock", "warning", "users", "stats", "ticket", "lock",
              "disable", "trash", "edit", "minus", "info", "msg", "reply")
})
# ساعت‌های دیگر هم همان ایموجی ساعت را می‌گیرند
tg.set_premium_map({
    **tg._PREMIUM_MAP,
    **{c: PREMIUM_EMOJI["clock"][1] for c in "🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚🕛"},
})


# کاربری که ربات را بلاک کرده باشد، با اولین ارسال ناموفق ثبت می‌شود و از پیام‌های همگانی بعدی کنار می‌ماند
tg.set_blocked_handler(lambda chat_id: db.mark_user_blocked(chat_id))


def _utf16_len(s):
    """طول رشته بر حسب واحدهای UTF-16؛ آفست/طول entity های تلگرام باید بر همین مبنا باشد
    (بیشتر ایموجی‌ها خارج از BMP هستند و ۲ واحد UTF-16 حساب می‌شوند، نه ۱)."""
    return len(s.encode("utf-16-le")) // 2


def build_message(parts):
    """پیام‌های ترکیبی (متن ساده + بولد + کد + ایموجی پریمیوم) را می‌سازد، بدون نیاز به parse_mode
    (چون entities و parse_mode هم‌زمان قابل استفاده نیستند). هر آیتم یکی از این‌هاست:
      - رشته‌ی معمولی
      - ("e", key)     ایموجی پریمیوم با کلید key از PREMIUM_EMOJI
      - ("b", text)    متن بولد
      - ("code", text) متن مونواسپیس/کد
    خروجی: (text, entities) که مستقیماً به tg.send_message(..., entities=entities) داده می‌شود."""
    text = ""
    entities = []
    for part in parts:
        if isinstance(part, str):
            text += part
            continue
        kind, value = part
        if kind == "e":
            char, cid = PREMIUM_EMOJI[value]
            offset = _utf16_len(text)
            text += char
            entities.append({
                "type": "custom_emoji",
                "offset": offset,
                "length": _utf16_len(char),
                "custom_emoji_id": cid,
            })
        elif kind == "b":
            offset = _utf16_len(text)
            text += value
            entities.append({"type": "bold", "offset": offset, "length": _utf16_len(value)})
        elif kind == "code":
            offset = _utf16_len(text)
            text += value
            entities.append({"type": "code", "offset": offset, "length": _utf16_len(value)})
        else:
            text += str(value)
    return text, entities


def btn(text, callback_data=None, emoji=None, url=None, style=None):
    """یک دکمه‌ی اینلاین می‌سازد. اگر emoji (کلیدی از PREMIUM_EMOJI) داده شود، ایموجی پریمیوم
    با فیلد icon_custom_emoji_id قبل از متن دکمه نمایش داده می‌شود؛ پس متن دکمه را بدون
    ایموجی بنویسید. اگر در config مقدار PREMIUM_BUTTON_ICONS=False باشد (مثلاً چون اکانت صاحب
    ربات پریمیوم نیست و تلگرام آیکون را نادیده می‌گیرد)، به‌جای آن ایموجی معمولی به اول متن
    اضافه می‌شود تا دکمه بی‌آیکون نماند."""
    button = {"text": text}
    if emoji:
        char, cid = PREMIUM_EMOJI[emoji]
        if getattr(config, "PREMIUM_BUTTON_ICONS", True):
            button["icon_custom_emoji_id"] = cid
        else:
            button["text"] = f"{char} {text}"
    if callback_data is not None:
        button["callback_data"] = callback_data
    if url is not None:
        button["url"] = url
    if style:
        # رنگ دکمه (Bot API 9.4): "success" = سبز، "danger" = قرمز، "primary" = آبی
        button["style"] = style
    return button


def emoji_html(key):
    """ایموجی پریمیوم برای پیام‌هایی که با parse_mode=HTML ارسال می‌شوند (تگ tg-emoji)."""
    char, cid = PREMIUM_EMOJI[key]
    return f'<tg-emoji emoji-id="{cid}">{char}</tg-emoji>'


def emoji_char(key):
    """فقط کاراکتر جایگزین یک ایموجی پریمیوم (بدون entity) - برای جاهایی که خودمان دستی
    entities را می‌سازیم یا فقط یک پیش‌نمایش ساده لازم است."""
    return PREMIUM_EMOJI[key][0]


def send_rich(chat_id, parts, reply_markup=None):
    """یک پیام با ایموجی پریمیوم/بولد/کد می‌سازد و می‌فرستد."""
    text, entities = build_message(parts)
    return tg.send_message(chat_id, text, reply_markup=reply_markup, entities=entities)


_bot_username_cache = {"value": config.BOT_USERNAME or None}


def get_bot_username():
    """نام کاربری ربات را برمی‌گرداند (برای ساخت لینک دعوت رفرال)؛ در صورت نبودن در config،
    یک‌بار از تلگرام (getMe) گرفته و کش می‌شود."""
    if _bot_username_cache["value"]:
        return _bot_username_cache["value"]
    try:
        resp = tg.get_me()
        if resp and resp.get("ok"):
            username = resp["result"].get("username")
            if username:
                _bot_username_cache["value"] = username
                return username
    except Exception:
        logger.exception("خطا در گرفتن نام کاربری ربات")
    return None


# ---------------------------------------------------------------------------
# استعلام قیمت لحظه‌ای تتر (برای بخش یوسی پابجی) از صرافی Wallex
# ---------------------------------------------------------------------------

def get_usdt_price():
    """قیمت لحظه‌ای تتر به تومان را از صرافی Wallex می‌گیرد. در صورت خطا None برمی‌گرداند."""
    try:
        url = "https://api.wallex.ir/v1/markets"
        r = requests.get(url, timeout=10).json()
        price = r["result"]["symbols"]["USDTTMN"]["stats"]["lastPrice"]
        return float(price)
    except Exception:
        logger.exception("خطا در دریافت قیمت تتر از Wallex")
        return None


def compute_uc_price(usdt_price, factor):
    """قیمت نهایی یک آیتم یوسی را بر اساس نرخ تتر و ضریب محاسبه و به نزدیک‌ترین هزار تومان به بالا رند می‌کند."""
    raw = usdt_price * factor
    return int(math.ceil(raw / 1000) * 1000)


def normalize_digits(text):
    """ارقام فارسی و عربی را به انگلیسی تبدیل می‌کند (برای تعداد و آیدی عددی پابجی)."""
    return (text or "").translate(_DIGIT_TRANS)


def extract_pubg_id(text):
    """آیدی عددی پابجی (فقط رقم، کامل، ۸ تا ۱۴ رقم) را از متن پیام کاربر بیرون می‌کشد؛ اگر نبود None."""
    m = PUBG_ID_RE.search(normalize_digits(text))
    return m.group(0) if m else None


_UC_ITEM_RE = re.compile(r"(\d[\d,]*)\s*UC(?:\s*[×xX]\s*(\d+))?", re.I)


def uc_amount(uc):
    """مقدار یوسی یک آیتم را از نامش می‌خواند (مثلاً «60 UC» -> 60)؛ اگر عددی نبود None."""
    m = re.search(r"\d[\d,]*", normalize_digits(uc.get("name") or ""))
    return int(m.group(0).replace(",", "")) if m else None


def uc_total_from_name(name):
    """جمع کل یوسی را از نام سفارش می‌خواند، مثلاً «60 UC × 2 + 325 UC» -> 445؛ اگر پیدا نشد None."""
    total, found = 0, False
    for amount, qty in _UC_ITEM_RE.findall(normalize_digits(name or "")):
        total += int(amount.replace(",", "")) * int(qty or 1)
        found = True
    return total if found else None


# نمونه‌ی درست پیام آیدی پابجی؛ در همه‌ی پیام‌های مرتبط یکسان نشان داده می‌شود
PUBG_ID_EXAMPLE = "آیدی: 5123456789\nنام: MyPubgName"


def pubg_prompt_text(reminder=False):
    """متن پررنگ و تاکیدی درخواست آیدی عددی پابجی. reminder=True یعنی یادآوری بعد از تایید سفارش."""
    if reminder:
        header = "🚨🚨🚨 <b>یادآوری فوری: آیدی عددی پابجی شما هنوز دریافت نشده است!</b> 🚨🚨🚨\n\n"
    else:
        header = "🚨🚨🚨 <b>مرحله‌ی بسیار مهم: ارسال آیدی عددی پابجی</b> 🚨🚨🚨\n\n"
    return (
        header
        + "🔢 همین الان <b>آیدی عددی (Character ID)</b> اکانت پابجی‌تان را در یک پیام برای من بفرستید.\n\n"
        "📍 آیدی عددی را کجا پیدا کنم؟\n"
        "داخل بازی روی <b>عکس پروفایل</b> خود بزنید؛ عددِ زیر نام شما همان آیدی عددی است.\n\n"
        "✅ نمونه‌ی درست:\n"
        f"<code>{PUBG_ID_EXAMPLE}</code>\n\n"
        "⛔️ <b>هشدار جدی:</b> اگر آیدی عددی <b>ارسال نشود</b> یا <b>ناقص / اشتباه</b> باشد، "
        "<b>سفارش شما انجام نخواهد شد!</b>\n\n"
        "✍️ لطفاً عدد را با دقت و کامل بفرستید (بدون فاصله و بدون رقم کم یا زیاد).\n"
        "(برای انصراف /cancel را بزنید)"
    )


def uc_total_line(order):
    """خط «مجموع یوسی» برای پیام‌های ادمین (خالی اگر از نام سفارش قابل خواندن نبود)."""
    total = uc_total_from_name(order.get("plan_name"))
    return f"🎯 مجموع یوسی: <b>{total:,} UC</b>\n" if total else ""


def pubg_id_mono_block(order):
    """بلوک آماده برای پیام‌های ادمین: آیدی عددی پابجی به‌صورت monospace (با یک لمس کپی می‌شود).
    اگر آیدی هنوز نرسیده باشد، هشدار برمی‌گرداند. خروجی HTML است."""
    info = order.get("pubg_info") or ""
    pid = extract_pubg_id(info)
    if not pid:
        return "⚠️ آیدی عددی پابجی هنوز از کاربر دریافت نشده؛ تا رسیدن آیدی واریز نکنید."
    text = f"🔢 آیدی عددی (برای کپی روی آن بزنید):\n<code>{pid}</code>"
    # اگر کاربر چیز دیگری (مثلاً نام اکانت) هم نوشته بود، بدون تکرار خود آیدی نشان می‌دهیم
    extra = normalize_digits(info).replace(pid, "")
    extra = re.sub(r"(آیدی|ایدی|id|ID|Id)\s*[:：]?", "", extra).strip(" \n:：-")
    if len(extra) > 1:
        text += f"\n📝 توضیح کاربر: {html.escape(extra)}"
    return text


def _ask_pubg_info(chat_id, order_id, reminder=False):
    """از خریدار آیدی عددی پابجی را می‌خواهد (پیام تاکیدی) و منتظر پاسخ او می‌ماند.
    در حالت یادآوری (reminder) اگر کاربر همان لحظه وسط مرحله‌ی دیگری از ربات است، آن مرحله را
    خراب نمی‌کنیم و از او می‌خواهیم آیدی را از طریق «ارتباط با ما» بفرستد."""
    text = pubg_prompt_text(reminder)
    if reminder:
        state = db.get_admin_state(chat_id)
        if state and state.get("action") not in (None, "await_pubg_info"):
            text = text.replace(
                "(برای انصراف /cancel را بزنید)",
                "برای ارسال، از منوی اصلی «ارتباط با ما» را بزنید و آیدی عددی را همان‌جا برای پشتیبانی بفرستید.",
            )
            tg.send_message(chat_id, text)
            return
    resp = tg.send_message(chat_id, text)
    _set_state_with_prompt(chat_id, "await_pubg_info", order_id, {}, _msg_id(resp))


# ---------------------------------------------------------------------------
# عضویت اجباری در کانال
# ⚠️ این دو مقدار را با یوزرنیم و لینک واقعی کانال «مورس شاپ» جایگزین کنید.
# یوزرنیم باید دقیقاً همانی باشد که ربات را به‌عنوان ادمین آن کانال اضافه کرده‌اید
# (بدون آن، دستور getChatMember همیشه خطا می‌دهد و بررسی عضویت کار نمی‌کند).
# ---------------------------------------------------------------------------
FORCE_JOIN_CHANNEL_USERNAME = "@morsshop_ir"          # مثال: "@morseshop"
FORCE_JOIN_CHANNEL_LINK = "https://t.me/morsshop_ir"  # لینک عمومی کانال برای دکمه‌ی عضویت
FORCE_JOIN_CHANNEL_TITLE = "مورس شاپ"

FORCE_JOIN_TEXT = (
    "🚫 برای استفاده از ربات، ابتدا باید عضو کانال <b>{title}</b> شوید.\n\n"
    "1️⃣ روی دکمه‌ی «عضویت در کانال» بزنید و عضو شوید.\n"
    "2️⃣ سپس روی دکمه‌ی «بررسی عضویت» بزنید."
).format(title=FORCE_JOIN_CHANNEL_TITLE)


def force_join_keyboard():
    return {
        "inline_keyboard": [
            [{"text": "📢 عضویت در کانال", "url": FORCE_JOIN_CHANNEL_LINK}],
            [btn("بررسی عضویت", "check_membership", "check")],
        ]
    }


def is_member_of_channel(user_id):
    """
    وضعیت عضویت کاربر در کانال اجباری را از API تلگرام می‌گیرد.
    خروجی: True اگر عضو/ادمین/سازنده‌ی کانال باشد، در غیر این صورت False.
    توجه: ربات باید در آن کانال به‌عنوان ادمین اضافه شده باشد.
    """
    try:
        result = tg._post(
            "getChatMember",
            chat_id=FORCE_JOIN_CHANNEL_USERNAME,
            user_id=user_id,
        )
    except Exception:
        logger.exception("خطا در بررسی عضویت کانال")
        return False

    if not result.get("ok"):
        logger.warning("getChatMember ناموفق بود: %s", result)
        return False

    status = result.get("result", {}).get("status")
    return status in ("member", "administrator", "creator")


def send_force_join_message(chat_id):
    tg.send_message(chat_id, FORCE_JOIN_TEXT, reply_markup=force_join_keyboard())


def send_start_menu(chat_id):
    """پیام خوش‌آمد و منوی اصلی ربات را می‌فرستد."""
    welcome_parts = [
        ("e", "shop"), f" به {config.SHOP_NAME} خوش آمدید!\n\n",
        "مرجع تخصصی خرید کانفیگ ", ("e", "gaming"), " وایرگارد و شارژ ", ("e", "uc"),
        " یوسی پابجی، با پرداخت امن (کارت‌به‌کارت یا ", ("e", "wallet"), " کیف پول داخلی) و پشتیبانی سریع.\n\n",
        "برای شروع، یکی از گزینه‌های منوی زیر را انتخاب کنید:",
    ]
    send_rich(chat_id, welcome_parts)
    home_text, home_entities = build_message([("e", "home"), " منوی اصلی — لطفاً یکی از گزینه‌های زیر را انتخاب کنید:"])
    tg.send_message(chat_id, home_text, reply_markup=main_menu_keyboard(is_admin(chat_id)), entities=home_entities)
    if is_admin(chat_id):
        admin_text, admin_entities = build_message([("e", "admin"), " برای مدیریت پلن‌ها و سفارش‌ها: /admin"])
        tg.send_message(chat_id, admin_text, entities=admin_entities)


# ---------------------------------------------------------------------------
# کمک‌کننده‌ها برای ساخت کیبورد
# ---------------------------------------------------------------------------

def nav_row(back_callback):
    """ردیف مشترک «بازگشت» (یک مرحله بالاتر) و «منوی اصلی» که در همه‌ی منوها
    به‌جز خود منوی اصلی نمایش داده می‌شود.
    ایموجی پریمیوم این دکمه‌ها از طریق icon_custom_emoji_id (تابع btn) نمایش داده می‌شود."""
    return [
        btn("بازگشت", back_callback, "back"),
        btn("منوی اصلی", "back_main", "home"),
    ]


TUTORIAL_PLATFORMS = {"android": "Android", "ios": "iOS", "windows": "Windows"}
TUTORIAL_DEFAULT = "آموزش این بخش هنوز ثبت نشده است. لطفاً از «ارتباط با ما» با پشتیبانی در تماس باشید."


def tutorial_menu_keyboard():
    rows = [[btn(label, f"tutorial:{key}", key)] for key, label in TUTORIAL_PLATFORMS.items()]
    rows.append(nav_row("back_main"))
    return {"inline_keyboard": rows}


TUTORIAL_MAX_MEDIA = 10


def _tutorial_media(platform):
    try:
        return json.loads(db.get_setting(f"tutorial_media_{platform}") or "[]")
    except (ValueError, TypeError):
        return []


def tutorial_page(platform, admin=False):
    """(متن، کیبورد) آموزش اتصال یک پلتفرم؛ ادمین دکمه‌های ویرایش را هم می‌بیند."""
    media = _tutorial_media(platform)
    body = db.get_setting(f"tutorial_{platform}") or ("" if media else TUTORIAL_DEFAULT)
    rows = []
    if admin:
        rows.append([btn("ویرایش / افزودن محتوا", f"tutorial_edit:{platform}", "edit")])
        if media:
            rows.append([btn(f"حذف همه‌ی ویدیوها و فایل‌ها ({len(media)})", f"tutorial_clearmedia:{platform}", "trash")])
    rows.append(nav_row("tutorial_menu"))
    text = f"آموزش اتصال — {TUTORIAL_PLATFORMS[platform]}" + (f"\n\n{body}" if body else "")
    return text, {"inline_keyboard": rows}


def _send_tutorial(chat_id, message_id, platform, admin=False):
    """صفحه‌ی آموزش را نشان می‌دهد: اگر ویدیو/فایل دارد اول آن‌ها و بعد متن با دکمه‌ها."""
    text, kb = tutorial_page(platform, admin=admin)
    media = _tutorial_media(platform)
    if not media:
        show_screen(chat_id, message_id, text, kb, parse_mode=None)
        return
    if message_id:
        tg.delete_message(chat_id, message_id)
    for item in media:
        tg.send_media_by_file_id(chat_id, item["type"], item["file_id"], caption=item.get("caption") or None)
    tg.send_message(chat_id, text, reply_markup=kb, parse_mode=None)


def _handle_tutorial_media(chat_id, message, state):
    """ادمین در حالت ویرایش آموزش، ویدیو/عکس/فایل فرستاده؛ به آموزش آن پلتفرم اضافه می‌شود."""
    platform = (state.get("data") or {}).get("platform")
    if platform not in TUTORIAL_PLATFORMS:
        db.clear_admin_state(chat_id)
        return
    kind, file_id = _extract_media(message)
    if not file_id:
        return
    media = _tutorial_media(platform)
    if len(media) >= TUTORIAL_MAX_MEDIA:
        tg.send_message(chat_id, f"حداکثر {TUTORIAL_MAX_MEDIA} ویدیو/فایل برای هر آموزش. اول «حذف همه» را بزنید.")
        return
    media.append({"type": kind, "file_id": file_id, "caption": (message.get("caption") or "").strip()})
    db.set_setting(f"tutorial_media_{platform}", json.dumps(media))
    _ack_message(chat_id, message.get("message_id"), f"✅ اضافه شد ({len(media)} مورد).")


def main_menu_keyboard(admin=False):
    """منوی اصلی ربات. برای ادمین یک دکمه‌ی «پنل مدیریت» هم اضافه می‌شود تا رفت‌وبرگشت
    بین پنل مدیریت و منوی اصلی بدون تایپ /admin ممکن باشد."""
    rows = [
        [btn("فروشگاه", "shop_menu", "shop", style="success")],
        [btn("کیف پول", "wallet_menu", "wallet"), btn("سفارش‌های من", "my_orders", "pending")],
        [btn("کسب درآمد", "referral_info", "invite"), btn("ارتباط با ما", "contact_us", "contact")],
        [btn("آموزش اتصال", "tutorial_menu", "tutorial")],
    ]
    if admin:
        rows.append([btn("پنل مدیریت", "admin_menu", "admin")])
    return {"inline_keyboard": rows}


def shop_menu_keyboard():
    """منوی فروشگاه: دسته‌بندی‌های اصلی محصولات (اشتراک گیمینگ / یوسی پابجی)."""
    buttons = [
        [btn("اشتراک گیمینگ", "maincat:gaming", "gaming", style="success")],
        [btn("یوسی پابجی", "maincat:uc", "uc", style="danger")],
    ]
    buttons.append(nav_row("back_main"))
    return {"inline_keyboard": buttons}


def wallet_menu_keyboard():
    """منوی کیف پول: دکمه‌ی شارژ + کسب درآمد + ناوبری."""
    buttons = [
        [btn("شارژ کیف پول", "wallet_topup_start", "wallet")],
        [btn("تاریخچه تراکنش‌ها", "wallet_history", "history")],
    ]
    buttons.append(nav_row("back_main"))
    return {"inline_keyboard": buttons}


def ticket_after_reply_keyboard(ticket_id):
    """زیر پاسخ پشتیبانی: کاربر یا گفتگو را ادامه می‌دهد یا (اگر مشکلش حل شده) تیکت را می‌بندد."""
    return {"inline_keyboard": [
        [btn("ادامه گفتگو (ارسال پیام)", f"ticket_continue:{ticket_id}", "contact")],
        [btn("مشکلم حل شد، بستن تیکت", f"ticket_close_user:{ticket_id}", "lock")],
    ]}


def ticket_admin_initiated_keyboard(ticket_id):
    """زیر پیامی که ادمین خودش شروع کرده (مشتری تیکت نزده): فقط دکمه‌ی پاسخ به پشتیبانی."""
    return {"inline_keyboard": [
        [btn("پاسخ به پشتیبانی", f"ticket_continue:{ticket_id}", "contact")],
    ]}


def _ticket_auto_close_label():
    hours = TICKET_AUTO_CLOSE_SECONDS // 3600
    return "۱ روز" if hours == 24 else ("۲ روز" if hours == 48 else f"{hours} ساعت")


def contact_us_keyboard():
    """منوی ارتباط با ما."""
    buttons = [
        [btn("شروع تیکت پشتیبانی", "ticket_start", "ticket")],
    ]
    buttons.append(nav_row("back_main"))
    return {"inline_keyboard": buttons}


def categories_keyboard():
    """کیبورد لیست مدت‌های اشتراک (مثلاً یک‌ماهه/دوماهه/سه‌ماهه) برای کاربر.
    همه‌ی مدت‌های فعال نشان داده می‌شوند؛ اگر مدتی هنوز پلنی نداشته باشد،
    با کلیک روی آن پیام «فعلاً پلنی موجود نیست» به کاربر نشان داده می‌شود."""
    categories = db.list_active_categories()
    buttons = []
    for c in categories:
        buttons.append([btn(c['name'], f"cat:{c['id']}", "duration")])
    # پلن‌هایی که به هیچ مدت اشتراکی اختصاص داده نشده‌اند (مثلاً پلن‌های قدیمی)
    if db.count_active_plans_in_category(None) > 0:
        buttons.append([{"text": "📦 سایر پلن‌ها", "callback_data": "cat:none"}])
    if not buttons:
        buttons = [[{"text": "فعلاً پلنی موجود نیست", "callback_data": "noop"}]]
    buttons.append(nav_row("shop_menu"))
    return {"inline_keyboard": buttons}


def plans_keyboard(category_id):
    """کیبورد پلن‌های داخل یک مدت اشتراک مشخص، با دکمه‌ی بازگشت."""
    plans = db.list_active_plans_by_category(category_id)
    buttons = []
    for p in plans:
        label = f"{p['name']} - {p['price']:,} تومان"
        buttons.append([{"text": label, "callback_data": f"buy:{p['id']}"}])
    if not buttons:
        buttons = [[{"text": "فعلاً پلنی در این مدت اشتراک نیست", "callback_data": "noop"}]]
    buttons.append(nav_row("back_categories"))
    return {"inline_keyboard": buttons}


def uc_products_keyboard(usdt_price, cart_count=0):
    """کیبورد لیست آیتم‌های یوسی پابجی، هرکدام با قیمت لحظه‌ای محاسبه‌شده.
    اگر سبد خرید کاربر خالی نباشد، دکمه‌ی «سبد خرید» با تعداد آیتم‌ها هم نشان داده می‌شود."""
    products = db.list_active_uc_products()
    buttons = []
    for p in products:
        price = compute_uc_price(usdt_price, p["factor"])
        label = f"{p['name']} ➜ {price:,} تومان"
        buttons.append([{"text": label, "callback_data": f"buyuc:{p['id']}"}])
    if not buttons:
        buttons = [[{"text": "فعلاً محصولی موجود نیست", "callback_data": "noop"}]]
    if cart_count:
        buttons.append([btn(f"سبد خرید ({cart_count})", "uccart", "shop")])
    buttons.append([btn("آپدیت قیمت‌ها", "maincat:uc", "refresh")])
    buttons.append(nav_row("shop_menu"))
    return {"inline_keyboard": buttons}


def uc_quantity_keyboard(uc_id):
    """کیبورد انتخاب تعداد برای یک آیتم یوسی: دکمه‌های ۱ تا ۱۰ و بازگشت."""
    row_size = 5
    quick = [{"text": str(n), "callback_data": f"buyucq:{uc_id}:{n}"} for n in UC_QUICK_QUANTITIES]
    rows = [quick[i:i + row_size] for i in range(0, len(quick), row_size)]
    rows.append(nav_row("maincat:uc"))
    return {"inline_keyboard": rows}


def uc_quantity_prompt_text(uc):
    """متن مرحله‌ی انتخاب تعداد، همراه با یک مثال."""
    return (
        f"🎯 آیتم انتخابی: <b>{html.escape(uc['name'])}</b>\n\n"
        "🔢 لطفاً <b>تعداد</b> مورد نظر را انتخاب کنید.\n\n"
        "💡 به‌عنوان مثال: برای خرید <b>۱۲۰ یوسی</b>، آیتم «۶۰ یوسی» را انتخاب کنید "
        "و تعداد را <b>۲</b> بزنید.\n\n"
        "🛍 بعد از انتخاب تعداد، خلاصه‌ی سفارش را می‌بینید؛ می‌توانید همان‌جا پرداخت کنید "
        "یا بسته‌ی یوسی دیگری هم به سفارش اضافه کنید."
    )


def cancel_order_row(order_id):
    """دکمه‌ی «لغو سفارش» که روی همه‌ی مراحل پرداخت (تا قبل از فرستادن رسید) نمایش داده می‌شود."""
    return [btn("لغو سفارش", f"cancel_order:{order_id}", "cross")]


def cancel_order_keyboard(order_id):
    return {"inline_keyboard": [cancel_order_row(order_id)]}


def payment_method_keyboard(order_id):
    """کیبورد انتخاب روش پرداخت برای سفارش‌های خرید (پلن/یوسی)."""
    return {
        "inline_keyboard": [
            [btn("پرداخت از کیف پول", f"pay_wallet:{order_id}", "wallet")],
            [btn("کارت به کارت", f"pay_card:{order_id}", "card")],
            cancel_order_row(order_id),
        ]
    }


KYC_DEFAULT_THRESHOLD = 2000000


def get_topup_bonus():
    """(حداقل مبلغ شارژ, مبلغ هدیه) از تنظیمات؛ اگر هر کدام صفر باشد آفر غیرفعال است."""
    try:
        mn = int(db.get_setting("topup_bonus_min", "0"))
        amt = int(db.get_setting("topup_bonus_amount", "0"))
    except (TypeError, ValueError):
        return 0, 0
    return (mn, amt) if mn > 0 and amt > 0 else (0, 0)


def topup_bonus_for(amount):
    mn, bonus = get_topup_bonus()
    return bonus if mn and amount >= mn else 0


def topup_offer_line():
    mn, bonus = get_topup_bonus()
    if not mn:
        return ""
    return (f"🎁 با شارژ {mn:,} تومان یا بیشتر، {bonus:,} تومان هدیه می‌گیرید "
            f"(هدیه فقط برای خرید پلن/کانفیگ قابل استفاده است، نه یوسی).")


def get_kyc_threshold():
    """مبلغ (تومان) که پرداخت‌های بیشتر از آن نیاز به احراز هویت دارند. 0 = غیرفعال."""
    try:
        return int(db.get_setting("kyc_threshold", str(KYC_DEFAULT_THRESHOLD)))
    except (TypeError, ValueError):
        return KYC_DEFAULT_THRESHOLD


def kyc_needed(order):
    thr = get_kyc_threshold()
    return thr > 0 and int(order.get("price") or 0) > thr


def admin_review_keyboard(order):
    """کیبورد تایید/رد برای ادمین. متن دکمه‌ی تایید بسته به نوع سفارش فرق می‌کند."""
    order_type = order.get("order_type") if isinstance(order, dict) else None
    if order_type == "wallet_topup":
        approve_text = "تایید و شارژ کیف پول"
    elif order_type == "uc":
        approve_text = "تایید سفارش"
    else:
        approve_text = "تایید و ارسال کانفیگ"
    order_id = order["id"] if isinstance(order, dict) else order
    if isinstance(order, dict) and order.get("kyc_required"):
        # سفارش با احراز هویت: رد رسید و رد احراز هویت جدا هستند تا کاربر بفهمد دلیل رد کدام بوده
        return {
            "inline_keyboard": [
                [{"text": approve_text, "callback_data": f"approve:{order_id}", "icon_custom_emoji_id": PREMIUM_EMOJI["check"][1]}],
                [
                    {"text": "رد رسید", "callback_data": f"reject:{order_id}:rcpt", "icon_custom_emoji_id": PREMIUM_EMOJI["cross"][1]},
                    {"text": "رد احراز هویت", "callback_data": f"reject:{order_id}:kyc", "icon_custom_emoji_id": PREMIUM_EMOJI["cross"][1]},
                ],
                [btn("پیام به کاربر", f"msguser:{order['user_id']}", "contact"), btn("پروفایل", f"admin_wuser:{order['user_id']}:n", "users")],
            ]
        }
    rows = [
        [
            {"text": approve_text, "callback_data": f"approve:{order_id}", "icon_custom_emoji_id": PREMIUM_EMOJI["check"][1]},
            {"text": "رد سفارش", "callback_data": f"reject:{order_id}", "icon_custom_emoji_id": PREMIUM_EMOJI["cross"][1]},
        ]
    ]
    if isinstance(order, dict) and order.get("user_id"):
        rows.append([btn("پیام به کاربر", f"msguser:{order['user_id']}", "contact"), btn("پروفایل", f"admin_wuser:{order['user_id']}:n", "users")])
    return {"inline_keyboard": rows}


def admin_menu_keyboard(admin_id=None):
    """پنل اصلی ادمین (خلاصه): فروش یک‌جا در «مدیریت فروش» جمع شده و روی دکمه‌های سفارش/تیکت
    تعداد موارد منتظر نمایش داده می‌شود."""
    try:
        pending = len(db.list_pending_orders())
        tickets = len(db.list_open_tickets())
    except Exception:
        pending = tickets = 0

    def with_count(label, n):
        return f"{label} ({n})" if n else label

    return {
        "inline_keyboard": [
            [btn("مدیریت فروش", "admin_sales", "admin")],
            [
                btn(with_count("سفارش‌های در انتظار", pending), "admin_pending", "pending"),
                btn(with_count("تیکت‌های باز", tickets), "admin_tickets", "ticket"),
            ],
            [
                btn("گزارش فروش", "admin_report", "stats"),
                btn("تعداد کاربران", "admin_userscount", "users"),
            ],
            [btn("حد مبلغ احراز هویت", "admin_kyc_start", "card")],
            [btn("پروفایل کاربر", "admin_wallet_start", "users")],
            [btn("پیام همگانی", "admin_broadcast_start", "broadcast")],
        ] + ([[btn("مدیریت ادمین‌ها", "admin_admins", "admin")]] if admin_id is not None and is_main_admin(admin_id) else [])
        + [[btn("منوی اصلی", "back_main", "home")]]
    }


# ---------------- توقف/ازسرگیری فروش (جدا برای کانفیگ و یوسی) ----------------
SALES_KINDS = {
    "gaming": "کانفیگ",
    "uc": "یوسی",
}


def is_sales_paused(kind):
    """آیا فروش این دسته (gaming = کانفیگ، uc = یوسی) توسط ادمین متوقف شده است؟"""
    try:
        return db.get_setting(f"sales_paused_{kind}", "0") == "1"
    except Exception:
        return False


def sales_paused_reason(kind):
    try:
        return (db.get_setting(f"sales_paused_reason_{kind}", "") or "").strip()
    except Exception:
        return ""


def sales_paused_text(kind, escape=True):
    """متن «فروش متوقف است» برای کاربر؛ اگر ادمین موقع توقف دلیل نوشته باشد، همان نمایش داده می‌شود."""
    text = f"⏸ فعلاً فروش {SALES_KINDS[kind]} متوقف شده است."
    reason = sales_paused_reason(kind)
    if reason:
        text += f"\n\n📝 توضیحات: {html.escape(reason) if escape else reason}"
    text += "\n\nلطفاً بعداً دوباره مراجعه کنید. 🙏"
    return text


def _apply_pause(admin_chat_id, kind, reason=""):
    """فروش یک دسته را متوقف می‌کند (با توضیح اختیاری) و صفحه‌ی مدیریت فروش را به ادمین نشان می‌دهد."""
    db.set_setting(f"sales_paused_{kind}", "1")
    db.set_setting(f"sales_paused_reason_{kind}", reason or "")
    db.clear_admin_state(admin_chat_id)
    text, entities = _admin_sales_text()
    tg.send_message(admin_chat_id, text, reply_markup=admin_sales_keyboard(), entities=entities)


def _order_sales_kind(order):
    """دسته‌ی فروشِ یک سفارش: پلن -> gaming، یوسی -> uc، شارژ کیف پول -> None (هیچ‌وقت متوقف نمی‌شود)."""
    order_type = (order.get("order_type") if isinstance(order, dict) else None) or "plan"
    if order_type == "uc":
        return "uc"
    if order_type == "wallet_topup":
        return None
    return "gaming"


def _block_if_paused(cq_id, kind, chat_id=None):
    """اگر فروش متوقف باشد به کاربر پیام «متوقف شده» + دکمه‌ی «خبرم کن» می‌دهد و True برمی‌گرداند."""
    if not (kind and is_sales_paused(kind)):
        return False
    if chat_id is None:
        tg.answer_callback_query(cq_id, sales_paused_text(kind, escape=False), show_alert=True)
        return True
    tg.answer_callback_query(cq_id)
    already = db.has_sales_notify(chat_id, kind)
    rows = []
    if not already:
        rows.append([btn("وقتی فروش شروع شد خبرم کن", f"notifyme:{kind}", "notify")])
    rows.append([btn("منوی اصلی", "back_main", "home")])
    text = sales_paused_text(kind)
    if already:
        text += "\n\n🔔 شما در لیست اطلاع‌رسانی هستید؛ به‌محض شروع فروش خبرتان می‌کنیم."
    tg.send_message(chat_id, text, reply_markup={"inline_keyboard": rows})
    return True


def _notify_sales_resumed(kind):
    """بعد از ازسرگیری فروش، به همه‌ی کاربرانِ ثبت‌شده در لیست «خبرم کن» پیام می‌دهد."""
    def run():
        label = SALES_KINDS[kind]
        for uid in db.pop_sales_notify(kind):
            try:
                tg.send_message(
                    uid,
                    f"✅ فروش {label} دوباره شروع شد!\nبرای خرید به منوی اصلی بروید.",
                    reply_markup={"inline_keyboard": [[btn("منوی اصلی", "back_main", "home")]]},
                )
            except Exception:
                logger.exception("ارسال اعلان ازسرگیری فروش به %s ناموفق بود", uid)
            time.sleep(0.05)
    threading.Thread(target=run, daemon=True).start()


def admin_sales_keyboard():
    """زیرمنوی «مدیریت فروش»: هر بخش داخل خودش «افزودن» و «ویرایش» دارد."""
    toggle_rows = []
    for kind, label in SALES_KINDS.items():
        if is_sales_paused(kind):
            toggle_rows.append([btn(f"ازسرگیری فروش {label}", f"admin_togglesale:{kind}", "check")])
        else:
            toggle_rows.append([btn(f"توقف فروش {label}", f"admin_togglesale:{kind}", "disable")])
    return {
        "inline_keyboard": toggle_rows + [
            [btn("مدیریت مدت‌های اشتراک", "admin_sec:cats", "admin")],
            [btn("مدیریت پلن‌ها", "admin_sec:plans", "admin")],
            [btn("مدیریت ضرایب یوسی پابجی", "admin_sec:uc", "admin")],
            [btn("آفرها", "admin_offers", "admin")],
            [btn("بازگشت به پنل مدیریت", "admin_menu", "back")],
        ]
    }


# بخش‌های «مدیریت فروش»: عنوان، دکمه‌ی افزودن، دکمه‌ی ویرایش (لیست + ویرایش/فعال‌سازی/حذف)
ADMIN_SECTIONS = {
    "cats":  {"title": "مدت‌های اشتراک", "add_cb": "admin_addcat_start",  "list_cb": "admin_listcats",
              "items": lambda: db.list_all_categories()},
    "plans": {"title": "پلن‌ها", "add_cb": "admin_addplan_start", "list_cb": "admin_listplans",
              "items": lambda: db.list_all_plans()},
    "codes": {"title": "کدهای تخفیف", "add_cb": "admin_addcode_start", "list_cb": "admin_listcodes",
              "items": lambda: db.list_all_discount_codes()},
    "uc":    {"title": "ضرایب یوسی پابجی", "add_cb": "admin_adduc_start", "list_cb": "admin_listuc",
              "items": lambda: db.list_all_uc_products()},
}


def admin_offers_keyboard():
    """زیرمنوی «آفرها» داخل مدیریت فروش."""
    return {"inline_keyboard": [
        [btn("تنظیم درصد رفرال", "admin_setreferral_start", "invite")],
        [btn("تنظیم آفر هدیه‌ی شارژ", "admin_bonus_start", "wallet")],
        [btn("مدیریت کدهای تخفیف", "admin_sec:codes", "discount")],
        [btn("بازگشت", "admin_sales", "back"), btn("پنل مدیریت", "admin_menu", "admin")],
    ]}


def _admin_offers_text():
    pct = db.get_referral_percent()
    ref = f"{pct:g}٪" if pct > 0 else "غیرفعال"
    mn, bonus = get_topup_bonus()
    return build_message([
        ("e", "admin"), " ", ("b", "آفرها"), "\n\n",
        f"🎁 درصد رفرال: {ref}\n",
        (f"💰 آفر هدیه‌ی شارژ: شارژ {mn:,} تومان یا بیشتر ← {bonus:,} تومان هدیه\n" if mn else "💰 آفر هدیه‌ی شارژ: غیرفعال\n"),
        f"🏷 کدهای تخفیف فعال: {sum(1 for c in db.list_all_discount_codes() if c.get('active'))}",
    ])


def admin_section_keyboard(key):
    sec = ADMIN_SECTIONS[key]
    back_cb = "admin_offers" if key == "codes" else "admin_sales"
    return {
        "inline_keyboard": [
            [btn("افزودن", sec["add_cb"], "plus"), btn("ویرایش", sec["list_cb"], "edit")],
            [btn("بازگشت", back_cb, "back"), btn("پنل مدیریت", "admin_menu", "admin")],
        ]
    }


def admin_section_text(key):
    sec = ADMIN_SECTIONS[key]
    items = sec["items"]()
    total = len(items)
    active = sum(1 for i in items if i.get("active"))
    return build_message([
        ("e", "admin"), " ", ("b", f"مدیریت {sec['title']}"), "\n\n",
        f"تعداد کل: {total} | فعال: {active}\n\n",
        "برای ثبت مورد جدید «افزودن» و برای ویرایش، فعال/غیرفعال‌کردن یا حذف «ویرایش» را بزنید.",
    ])


def _admin_sales_text():
    status = " | ".join(
        f"{label}: {'⏸ متوقف' if is_sales_paused(kind) else '✅ فعال'}" for kind, label in SALES_KINDS.items()
    )
    reasons = "".join(
        f"\n📝 توضیح توقف {label}: {sales_paused_reason(kind)}"
        for kind, label in SALES_KINDS.items() if is_sales_paused(kind) and sales_paused_reason(kind)
    )
    return build_message([
        ("e", "admin"), " ", ("b", "مدیریت فروش"), "\n\n",
        f"وضعیت فروش — {status}{reasons}\n\nبخش مورد نظر را انتخاب کنید:",
    ])


def admin_home_text():
    return build_message([("e", "admin"), " ", ("b", "پنل مدیریت"), "\n\nبخش مورد نظر را انتخاب کنید:"])


def _admin_display(uid):
    u = db.get_user_row(uid)
    if u and u.get("username"):
        return f"@{u['username']}"
    if u and u.get("full_name"):
        return u["full_name"]
    return str(uid)


def _show_admins_screen(chat_id, message_id=None):
    extra = get_extra_admin_ids()
    text = (f"🛠 <b>مدیریت ادمین‌ها</b>\n\nادمین اصلی: <code>{MAIN_ADMIN_ID}</code> ({html.escape(_admin_display(MAIN_ADMIN_ID))})\n"
            + ("\nادمین‌های دیگر (برای حذف روی نامشان بزنید):" if extra else "\nادمین دیگری ثبت نشده."))
    rows = [[btn(f"حذف {_admin_display(a)} ({a})", f"admin_rm:{a}", "trash")] for a in extra]
    rows.append([btn("افزودن ادمین", "admin_add_start", "plus")])
    rows.append([btn("پنل مدیریت", "admin_menu", "admin")])
    show_screen(chat_id, message_id, text, {"inline_keyboard": rows})


def _handle_admin_add(chat_id, text):
    raw = text.strip().lstrip("@").translate(_DIGIT_TRANS)
    if raw.isdigit():
        uid = int(raw)
    else:
        found = db.find_user_by_username(raw)
        uid = found["user_id"] if found else None
    if uid is None:
        _prompt_error(chat_id, "کاربر پیدا نشد. آیدی عددی را بفرستید یا /cancel بزنید.")
        return
    _drop_prompts(chat_id)
    db.clear_admin_state(chat_id)
    if uid in get_admin_ids():
        tg.send_message(chat_id, "این کاربر از قبل ادمین است.")
    else:
        save_extra_admin_ids(get_extra_admin_ids() + [uid])
        tg.send_message(uid, "✅ شما به‌عنوان ادمین ربات اضافه شدید. برای ورود به پنل: /admin")
    _show_admins_screen(chat_id)


def _show_admin_screen(chat_id, message_id, text, entities, kb):
    """صفحه‌ی منو را روی همان پیام ویرایش می‌کند (بدون شلوغ‌شدن چت)؛ اگر نشد پیام جدید می‌فرستد."""
    if message_id:
        resp = tg.edit_message_text(chat_id, message_id, text, reply_markup=kb, entities=entities)
        if resp.get("ok") or "not modified" in str(resp.get("description", "")):
            return
    tg.send_message(chat_id, text, reply_markup=kb, entities=entities)


def _send_admin_list_footer(chat_id, key):
    """بعد از لیست آیتم‌ها یک دکمه‌ی برگشت می‌فرستد تا ادمین مجبور نشود /admin بزند."""
    kb = {"inline_keyboard": [[
        btn("بازگشت", f"admin_sec:{key}", "back"),
        btn("پنل مدیریت", "admin_menu", "admin"),
    ]]}
    text, entities = build_message([("e", "admin"), " پایان لیست."])
    tg.send_message(chat_id, text, reply_markup=kb, entities=entities)


def category_row_keyboard(cat):
    if cat["active"]:
        toggle_btn = btn("غیرفعال کن", f"toggle_cat:{cat['id']}:off", "disable")
    else:
        toggle_btn = {"text": "♻️ فعال کن", "callback_data": f"toggle_cat:{cat['id']}:on"}
    edit_btn = btn("ویرایش", f"editcat_start:{cat['id']}", "edit")
    delete_btn = btn("حذف مدت اشتراک", f"delcat_ask:{cat['id']}", "trash")
    return {"inline_keyboard": [[toggle_btn], [edit_btn], [delete_btn]]}


def plan_row_keyboard(plan):
    if plan["active"]:
        toggle_btn = btn("غیرفعال کن", f"toggle_plan:{plan['id']}:off", "disable")
    else:
        toggle_btn = {"text": "♻️ فعال کن", "callback_data": f"toggle_plan:{plan['id']}:on"}
    edit_btn = btn("ویرایش", f"editplan_start:{plan['id']}", "edit")
    delete_btn = btn("حذف کامل", f"delplan_ask:{plan['id']}", "trash")
    return {"inline_keyboard": [[toggle_btn], [edit_btn], [delete_btn]]}


def uc_row_keyboard(uc):
    if uc["active"]:
        toggle_btn = btn("غیرفعال کن", f"toggle_uc:{uc['id']}:off", "disable")
    else:
        toggle_btn = {"text": "♻️ فعال کن", "callback_data": f"toggle_uc:{uc['id']}:on"}
    edit_btn = btn("ویرایش ضریب", f"edituc_start:{uc['id']}", "edit")
    delete_btn = btn("حذف کامل", f"deluc_ask:{uc['id']}", "trash")
    return {"inline_keyboard": [[toggle_btn], [edit_btn], [delete_btn]]}


def discount_code_row_keyboard(code):
    if code["active"]:
        toggle_btn = btn("غیرفعال کن", f"toggle_code:{code['id']}:off", "disable")
    else:
        toggle_btn = {"text": "♻️ فعال کن", "callback_data": f"toggle_code:{code['id']}:on"}
    edit_btn = btn("ویرایش مقدار", f"editcode_start:{code['id']}", "edit")
    delete_btn = btn("حذف کامل", f"delcode_ask:{code['id']}", "trash")
    return {"inline_keyboard": [[toggle_btn], [edit_btn], [delete_btn]]}


def discount_choice_keyboard(plan_id):
    return {
        "inline_keyboard": [
            [btn("بله، کد تخفیف دارم", f"discount_yes:{plan_id}", "discount")],
            [btn("خیر، ادامه بدون تخفیف", f"discount_no:{plan_id}", "cross")],
        ]
    }


def discount_type_keyboard(plan_id=None):
    """اگر plan_id داده شود یعنی کاربر عادی وسط خرید است (استفاده نمی‌شود اینجا)،
    این کیبورد فقط برای ویزارد ادمین (ساخت کد تخفیف) استفاده می‌شود."""
    return {
        "inline_keyboard": [
            [{"text": "٪ درصدی", "callback_data": "addcode_type:percent"}],
            [{"text": "💵 مبلغ ثابت (تومان)", "callback_data": "addcode_type:amount"}],
        ]
    }


# ---------------------------------------------------------------------------
# لیست ادمین‌ها (چند ادمین) — از پنل مدیریت قابل افزودن/حذف است
# ---------------------------------------------------------------------------
# EXTRA_ADMIN_IDS فقط «مقدار اولیه» است: بار اول که ربات بالا می‌آید همین لیست داخل دیتابیس ذخیره
# می‌شود و از آن به بعد ادمین‌ها از خود پنل (پنل مدیریت ← مدیریت ادمین‌ها) اضافه و حذف می‌شوند؛
# دیگر لازم نیست فایل را دوباره آپلود کنید. (ادمین اصلی = ADMIN_CHAT_ID در config.py و حذف‌شدنی نیست.)
EXTRA_ADMIN_IDS = [
    189053046
]

MAIN_ADMIN_ID = int(config.ADMIN_CHAT_ID)
_ADMIN_CACHE = {"ts": 0.0, "extra": None}
_ADMIN_CACHE_TTL = 5  # ثانیه؛ برای حالتی که هاست چند پردازش (worker) بالا می‌آورد


def _read_extra_admin_ids():
    raw = db.get_setting("extra_admin_ids")
    if raw is None:
        ids = sorted({int(a) for a in EXTRA_ADMIN_IDS})
        db.set_setting("extra_admin_ids", json.dumps(ids))
        return ids
    try:
        return sorted({int(a) for a in json.loads(raw)})
    except (ValueError, TypeError):
        return []


def get_extra_admin_ids():
    now = time.time()
    if _ADMIN_CACHE["extra"] is None or now - _ADMIN_CACHE["ts"] > _ADMIN_CACHE_TTL:
        _ADMIN_CACHE["extra"] = _read_extra_admin_ids()
        _ADMIN_CACHE["ts"] = now
    return list(_ADMIN_CACHE["extra"])


def get_admin_ids():
    return {MAIN_ADMIN_ID} | set(get_extra_admin_ids())


def save_extra_admin_ids(ids):
    ids = sorted({int(a) for a in ids if int(a) != MAIN_ADMIN_ID})
    db.set_setting("extra_admin_ids", json.dumps(ids))
    _ADMIN_CACHE["extra"] = ids
    _ADMIN_CACHE["ts"] = time.time()


class _AdminIds:
    """شبیه یک set رفتار می‌کند (for / in / len) ولی همیشه از لیست زنده‌ی دیتابیس می‌خواند."""

    def __iter__(self):
        return iter(sorted(get_admin_ids()))

    def __contains__(self, item):
        try:
            return int(item) in get_admin_ids()
        except (TypeError, ValueError):
            return False

    def __len__(self):
        return len(get_admin_ids())


ADMIN_IDS = _AdminIds()


def is_admin(chat_id):
    return chat_id in ADMIN_IDS


def is_main_admin(chat_id):
    try:
        return int(chat_id) == MAIN_ADMIN_ID
    except (TypeError, ValueError):
        return False


# ---------------------------------------------------------------------------
# نمایش صفحه روی همان پیام (به‌جای پیام جدید) + پیام‌های راهنمای موقت که بعد از پاسخ پاک می‌شوند
# ---------------------------------------------------------------------------

EMPTY_KB = {"inline_keyboard": []}


def show_screen(chat_id, message_id, text, kb=None, entities=None, parse_mode="HTML"):
    """صفحه را روی همان پیام ویرایش می‌کند؛ اگر پیام قابل ویرایش نبود (مثلاً پیام عکس) پیام جدید می‌فرستد.
    بدون kb، دکمه‌های قبلی پیام برداشته می‌شود. خروجی: message_id پیامی که نمایش داده شد (یا None)."""
    if message_id:
        resp = tg.edit_message_text(
            chat_id, message_id, text,
            reply_markup=kb if kb is not None else EMPTY_KB,
            entities=entities, parse_mode=parse_mode,
        )
        if resp.get("ok") or "not modified" in str(resp.get("description", "")):
            return message_id
    resp = tg.send_message(chat_id, text, reply_markup=kb, entities=entities, parse_mode=parse_mode)
    try:
        return resp["result"]["message_id"] if resp.get("ok") else None
    except (KeyError, TypeError):
        return None


def show_rich(chat_id, message_id, parts, kb=None):
    text, entities = build_message(parts)
    return show_screen(chat_id, message_id, text, kb, entities)


# حالت‌هایی که فقط یک «پیام راهنمای موقت» هستند؛ اگر کاربر به‌جای جواب‌دادن دکمه‌ای بزند، لغو می‌شوند
SOFT_PROMPT_ACTIONS = ("await_discount_code", "await_uc_quantity", "await_wallet_topup_amount")


def _prompt_ids(state):
    return list(((state or {}).get("data") or {}).get("prompt_ids") or [])


def _set_state_with_prompt(chat_id, action, order_id=None, data=None, prompt_message_id=None):
    """وضعیت انتظار را ثبت می‌کند و شناسه‌ی پیام راهنما را کنارش نگه می‌دارد تا بعد از پاسخ مشتری پاک شود."""
    data = dict(data or {})
    if prompt_message_id:
        data["prompt_ids"] = [prompt_message_id]
    db.set_admin_state(chat_id, action, order_id, data)


def _remember_prompt(chat_id, resp):
    """پیام خطا/راهنمای تازه را هم به لیست پیام‌های موقت اضافه می‌کند (تا همه با هم پاک شوند)."""
    try:
        if not resp or not resp.get("ok"):
            return
        mid = resp["result"]["message_id"]
        st = db.get_admin_state(chat_id)
        if not st:
            return
        data = dict(st.get("data") or {})
        ids = list(data.get("prompt_ids") or [])
        ids.append(mid)
        data["prompt_ids"] = ids[-8:]
        db.set_admin_state(chat_id, st["action"], st.get("order_id"), data)
    except Exception:
        logger.exception("remember_prompt failed")


def _drop_prompts(chat_id, state=None, keep_message_id=None):
    """پیام‌های راهنمای موقتِ این حالت را پاک می‌کند (به‌جز keep_message_id اگر داده شود)."""
    st = state if state is not None else db.get_admin_state(chat_id)
    for mid in _prompt_ids(st):
        if keep_message_id and mid == keep_message_id:
            continue
        try:
            tg.delete_message(chat_id, mid)
        except Exception:
            pass


def _prompt_error(chat_id, text, **kwargs):
    """پیام خطای ورودی را می‌فرستد و آن را هم جزو پیام‌های موقت ثبت می‌کند."""
    resp = tg.send_message(chat_id, text, **kwargs)
    _remember_prompt(chat_id, resp)
    return resp


def _msg_id(resp):
    """شناسه‌ی پیام از جواب تلگرام (یا None)."""
    try:
        return resp["result"]["message_id"] if resp and resp.get("ok") else None
    except (KeyError, TypeError):
        return None


def _ack_message(chat_id, message_id, fallback_text):
    """تاییدیه‌ی کوچک برای پیامی که کاربر/ادمین تایپ کرده: به‌جای پیام جدید، روی همان پیام یک 👍 می‌گذارد.
    اگر تلگرام ریاکشن را نپذیرفت، پیام متنی معمولی می‌فرستد."""
    if message_id:
        try:
            resp = tg.set_message_reaction(chat_id, message_id, "👍")
            if resp and resp.get("ok"):
                return
        except Exception:
            logger.exception("set_message_reaction failed")
    tg.send_message(chat_id, fallback_text)


# ---------------------------------------------------------------------------
# پردازش پیام‌های متنی
# ---------------------------------------------------------------------------

def handle_text_message(message):
    chat_id = message["chat"]["id"]
    text = (message.get("text") or "").strip()
    from_user = message.get("from", {})

    # -------- اگر ادمین وسط ویزارد «افزودن/ویرایش پلن»، «افزودن/ویرایش مدت اشتراک»،
    # «افزودن/ویرایش کد تخفیف»، «افزودن/ویرایش آیتم یوسی» یا «تنظیم درصد رفرال» است --------
    if is_admin(chat_id) and not text.startswith("/"):
        state = db.get_admin_state(chat_id)
        if state and state.get("action") in (
            "await_plan_name", "await_plan_price", "await_plan_desc",
            "await_category_name", "await_category_duration",
            "await_code_text", "await_code_value",
            "await_uc_name", "await_uc_factor",
            "await_edit_category", "await_edit_plan", "await_edit_code_value", "await_edit_uc_factor",
            "await_referral_percent", "await_pause_reason", "await_kyc_threshold",
            "await_topup_bonus_min", "await_topup_bonus_amount", "await_tutorial_text",
            "await_wallet_user", "await_wallet_amount", "await_wallet_note", "await_msguser_lookup",
        ):
            _handle_plan_wizard_step(chat_id, text, state)
            return

    if is_main_admin(chat_id) and not text.startswith("/"):
        _st = db.get_admin_state(chat_id)
        if _st and _st.get("action") == "await_admin_add":
            _handle_admin_add(chat_id, text)
            return

    # -------- اگر ادمین وسط پاسخ‌دادن به یک تیکت پشتیبانی است --------
    if is_admin(chat_id) and not text.startswith("/"):
        admin_ticket_state = db.get_admin_state(chat_id)
        if admin_ticket_state and admin_ticket_state.get("action") == "await_ticket_reply":
            _handle_ticket_admin_reply(chat_id, text, admin_ticket_state, from_user, message.get("message_id"))
            return
        if admin_ticket_state and admin_ticket_state.get("action") == "await_support_msg":
            _handle_support_message_to_user(chat_id, text, admin_ticket_state, from_user, message.get("message_id"))
            return

    # -------- اگر کاربر (هرکسی، نه فقط ادمین) وسط نوشتن پیام برای تیکت پشتیبانی است --------
    if not text.startswith("/"):
        ticket_state = db.get_admin_state(chat_id)
        if ticket_state and ticket_state.get("action") == "await_ticket_message":
            _handle_ticket_user_message(chat_id, text, ticket_state, from_user, message.get("message_id"))
            return

    # -------- اگر کاربر (هرکسی، نه فقط ادمین) وسط وارد کردن کد تخفیف برای خریدش است --------
    if not text.startswith("/"):
        user_state = db.get_admin_state(chat_id)
        if user_state and user_state.get("action") == "await_discount_code":
            _handle_discount_code_entry(chat_id, text, user_state, from_user)
            return

    # -------- اگر کاربر وسط فرستادن آیدی/نام اکانت پابجی (بعد از ارسال رسید سفارش یوسی) است --------
    if not text.startswith("/"):
        user_state = db.get_admin_state(chat_id)
        if user_state and user_state.get("action") == "await_pubg_info":
            _handle_pubg_info_entry(chat_id, text, user_state, from_user)
            return

    # -------- اگر کاربر وسط وارد کردن «تعداد دیگر» برای خرید یوسی است --------
    if not text.startswith("/"):
        user_state = db.get_admin_state(chat_id)
        if user_state and user_state.get("action") == "await_uc_quantity":
            _handle_uc_quantity_entry(chat_id, text, user_state, from_user)
            return

    # -------- اگر کاربر وسط وارد کردن مبلغ شارژ کیف پول است --------
    if not text.startswith("/"):
        user_state = db.get_admin_state(chat_id)
        if user_state and user_state.get("action") == "await_wallet_topup_amount":
            _handle_wallet_topup_amount_entry(chat_id, text, from_user)
            return

    if text == "/cancel":
        user_state = db.get_admin_state(chat_id)
        if user_state:
            _drop_prompts(chat_id, user_state)  # پیام‌های راهنمای موقت این مرحله پاک می‌شود
        if user_state and user_state.get("action") == "await_discount_code":
            plan_id = (user_state.get("data") or {}).get("plan_id")
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "از وارد کردن کد تخفیف انصراف داده شد.")
            if plan_id:
                _create_order_and_show_payment(chat_id, from_user, plan_id)
            return
        if user_state and user_state.get("action") == "await_pubg_info":
            db.clear_admin_state(chat_id)
            tg.send_message(
                chat_id,
                "⚠️ آیدی عددی پابجی دریافت نشد.\n\n"
                "⛔️ <b>بدون آیدی عددی، سفارش شما انجام نمی‌شود.</b>\n"
                "لطفاً هرچه زودتر آیدی عددی خود را از منوی اصلی، بخش «ارتباط با ما» برای پشتیبانی بفرستید.",
            )
            return
        if user_state and user_state.get("action") == "await_uc_quantity":
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "❌ انتخاب تعداد لغو شد. برای خرید یوسی دوباره از /start شروع کنید.")
            return
        if user_state and user_state.get("action") == "await_wallet_topup_amount":
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "❌ شارژ کیف پول لغو شد.", reply_markup=wallet_menu_keyboard())
            return
        if user_state and user_state.get("action") == "await_ticket_message":
            db.clear_admin_state(chat_id)
            tg.send_message(
                chat_id,
                "از حالت نوشتن پیام خارج شدید. تیکت شما بسته نشده و باز است؛ "
                "برای ادامه از «ارتباط با ما» دوباره وارد شوید.",
            )
            return
        if is_admin(chat_id) and user_state and user_state.get("action") in ("await_ticket_reply", "await_support_msg"):
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "از حالت ارسال پیام خارج شدید.", reply_markup=admin_menu_keyboard(chat_id))
            return

    if text == "/start" or text.startswith("/start "):
        payload = text[len("/start"):].strip()
        if payload.startswith("ref_"):
            ref_part = payload[len("ref_"):]
            if ref_part.isdigit():
                referrer_id = int(ref_part)
                if db.set_referred_by(chat_id, referrer_id):
                    try:
                        tg.send_message(referrer_id, "🎉 یک کاربر جدید با لینک دعوت شما وارد ربات شد!")
                    except Exception:
                        pass
        # عضویت در کانال همین الان در webhook() چک شد، نیازی به چک دوباره نیست.
        send_start_menu(chat_id)
        return

    if text == "/plans":
        tg.send_message(chat_id, "🛍 فروشگاه:\nلطفاً دسته مورد نظر را انتخاب کنید:", reply_markup=shop_menu_keyboard())
        return

    # -------- دستورات مخصوص ادمین --------
    if is_admin(chat_id):
        if text == "/admin":
            _home_text, _home_entities = admin_home_text()
            tg.send_message(chat_id, _home_text, reply_markup=admin_menu_keyboard(chat_id), entities=_home_entities)
            return

        if text == "/cancel":
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "عملیات لغو شد.", reply_markup=admin_menu_keyboard(chat_id))
            return

        if text == "/backup":
            if int(chat_id) != int(config.ADMIN_CHAT_ID):
                tg.send_message(chat_id, "فقط ادمین اصلی می‌تواند بکاپ بگیرد.")
                return
            tg.send_message(chat_id, "⏳ در حال ساخت بکاپ...")
            if _send_backup(chat_id, manual=True):
                db.set_setting("last_backup_ts", str(int(time.time())))
            else:
                tg.send_message(chat_id, "❌ ساخت یا ارسال بکاپ ناموفق بود. لاگ سرور را ببینید.")
            return

        if text.startswith("/addcategory"):
            payload = text[len("/addcategory"):].strip()
            if not payload:
                tg.send_message(chat_id, "فرمت درست:\n/addcategory نام مدت اشتراک\nمثلاً: /addcategory یک ماهه\n\nیا برای روش راحت‌تر، /admin را بزنید.")
                return
            category_id = db.add_category(payload)
            tg.send_message(chat_id, f"✅ مدت اشتراک «{payload}» ساخته شد. شناسه: #{category_id}")
            return

        if text == "/categories":
            _send_admin_categories_list(chat_id)
            return

        if text.startswith("/addplan"):
            payload = text[len("/addplan"):].strip()
            parts = [p.strip() for p in payload.split("|")]
            if len(parts) < 3:
                tg.send_message(
                    chat_id,
                    "فرمت درست:\n/addplan شناسه_مدت_اشتراک | نام | قیمت | توضیح (اختیاری)\n"
                    "مثلاً: /addplan 1 | ۱۰ گیگ | 45000\n\n"
                    "برای دیدن شناسه‌ی مدت‌های اشتراک، /categories را بزنید.\n"
                    "یا برای روش راحت‌تر (بدون نیاز به شناسه)، /admin را بزنید.",
                )
                return
            try:
                category_id = int(parts[0])
            except ValueError:
                tg.send_message(chat_id, "شناسه‌ی مدت اشتراک باید عدد باشد. برای دیدن شناسه‌ها /categories را بزنید.")
                return
            if not db.get_category(category_id):
                tg.send_message(chat_id, "چنین مدت اشتراکی پیدا نشد. برای دیدن شناسه‌ها /categories را بزنید.")
                return
            name = parts[1]
            try:
                price = int(parts[2].replace(",", ""))
            except ValueError:
                tg.send_message(chat_id, "قیمت باید عدد باشد.")
                return
            description = parts[3] if len(parts) > 3 else ""
            plan_id = db.add_plan(name, price, description, category_id=category_id)
            tg.send_message(chat_id, f"✅ پلن ساخته شد. شناسه: {plan_id}")
            return

        if text == "/plansadmin" or text == "/plans_admin":
            _send_admin_plans_list(chat_id)
            return

        if text.startswith("/delplan"):
            payload = text[len("/delplan"):].strip()
            try:
                plan_id = int(payload)
            except ValueError:
                tg.send_message(chat_id, "فرمت درست: /delplan شناسه")
                return
            db.deactivate_plan(plan_id)
            tg.send_message(chat_id, f"پلن #{plan_id} غیرفعال شد. (برای حذف کامل و دائمی از /rmplan استفاده کنید)")
            return

        if text.startswith("/rmplan"):
            payload = text[len("/rmplan"):].strip()
            try:
                plan_id = int(payload)
            except ValueError:
                tg.send_message(chat_id, "فرمت درست: /rmplan شناسه\n(این دستور پلن را کامل و برای همیشه حذف می‌کند)")
                return
            db.delete_plan(plan_id)
            tg.send_message(chat_id, f"🗑 پلن #{plan_id} برای همیشه حذف شد.")
            return

        if text == "/pending":
            _send_pending_orders(chat_id)
            return

        if text.startswith("/order"):
            arg = text[len("/order"):].strip().lstrip("#").translate(_DIGIT_TRANS)
            if not arg.isdigit():
                tg.send_message(chat_id, "فرمت درست: /order شماره_سفارش\nمثلاً: /order 123")
                return
            _send_order_card(chat_id, int(arg))
            return

        if text.startswith("/user"):
            arg = text[len("/user"):].strip().lstrip("@").translate(_DIGIT_TRANS)
            if not arg:
                tg.send_message(chat_id, "فرمت درست: /user آیدی_عددی یا @یوزرنیم")
                return
            found = {"user_id": int(arg)} if arg.isdigit() else db.find_user_by_username(arg)
            if not found:
                tg.send_message(chat_id, "کاربر پیدا نشد.")
                return
            _show_admin_wallet_user(chat_id, found["user_id"])
            return

        if text.startswith("/ledger"):
            arg = text[len("/ledger"):].strip()
            if not arg.isdigit():
                tg.send_message(chat_id, "فرمت درست: /ledger آیدی_عددی_کاربر\nمثلاً: /ledger 123456789")
                return
            _send_wallet_ledger(chat_id, int(arg))
            return

        if text == "/report":
            _send_sales_report(chat_id)
            return

        if text == "/tickets":
            tickets = db.list_open_tickets()
            if not tickets:
                tg.send_message(chat_id, "🎫 هیچ تیکت بازی وجود ندارد.")
            else:
                for t in tickets:
                    messages = db.list_ticket_messages(t["id"], limit=5)
                    last_msgs = "\n".join(f"[{m['sender']}] {m['text'][:60]}" for m in messages[-3:])
                    caption = f"🎫 تیکت #{t['id']} | کاربر: {t['user_id']}\n\n{last_msgs}"
                    kb = {
                        "inline_keyboard": [[
                            btn("پاسخ", f"ticket_reply_start:{t['id']}", "reply"),
                            btn("بستن", f"ticket_close:{t['id']}", "lock"),
                        ], [btn("پروفایل", f"admin_wuser:{t['user_id']}:n", "users")]]
                    }
                    tg.send_message(chat_id, caption, reply_markup=kb)
            return

        if text == "/done":
            _finish_delivery(chat_id)
            return

        if text == "/broadcast":
            _start_broadcast_wizard(chat_id)
            return

    # پیام‌های دیگر
    tg.send_message(chat_id, "برای شروع دستور /start را بزنید.")


def _handle_plan_wizard_step(chat_id, text, state):
    """ادامه‌ی گفتگوی قدم‌به‌قدم افزودن پلن جدید یا افزودن مدت اشتراک جدید."""
    action = state["action"]
    data = state.get("data") or {}

    if action in ("await_wallet_user", "await_wallet_amount", "await_wallet_note", "await_msguser_lookup"):
        _handle_admin_wallet_text(chat_id, text, state)
        return

    if action == "await_pause_reason":
        kind = data.get("kind")
        reason = text.strip()
        if kind not in SALES_KINDS:
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "عملیات منقضی شد؛ دوباره از «مدیریت فروش» اقدام کنید.")
            return
        if not reason:
            tg.send_message(chat_id, "متن خالی است. توضیحات را بنویسید یا دکمه‌ی «بدون توضیح» را بزنید.")
            return
        if len(reason) > 300:
            tg.send_message(chat_id, f"متن خیلی طولانی است ({len(reason)} کاراکتر). لطفاً کوتاه‌تر (حداکثر ۳۰۰ کاراکتر) بفرستید.")
            return
        tg.send_message(chat_id, f"✅ فروش {SALES_KINDS[kind]} با توضیح زیر متوقف شد:\n«{html.escape(reason)}»")
        _apply_pause(chat_id, kind, reason)
        return

    if action == "await_tutorial_text":
        platform = (state.get("data") or {}).get("platform")
        if platform not in TUTORIAL_PLATFORMS:
            db.clear_admin_state(chat_id)
            return
        value = text.strip()
        if len(value) > 3500:
            tg.send_message(chat_id, f"متن خیلی بلند است ({len(value)} کاراکتر). حداکثر ۳۵۰۰ کاراکتر بفرستید.")
            return
        db.set_setting(f"tutorial_{platform}", "" if value == "-" else value)
        tg.send_message(
            chat_id,
            "✅ متن ذخیره شد. می‌توانید ویدیو یا فایل هم بفرستید، یا «پایان» را بزنید.",
            reply_markup={"inline_keyboard": [[btn("پایان و پیش‌نمایش", f"tutorial:{platform}", "check")]]},
        )
        return

    if action in ("await_topup_bonus_min", "await_topup_bonus_amount"):
        cleaned = text.strip().translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789"))
        cleaned = cleaned.replace(",", "").replace("٬", "").replace(" ", "")
        if not cleaned.isdigit():
            tg.send_message(chat_id, "لطفاً فقط یک عدد (تومان) بفرستید، مثلاً: 500000 (یا 0 برای غیرفعال‌کردن).")
            return
        value = int(cleaned)
        if action == "await_topup_bonus_min":
            if value <= 0:
                db.set_setting("topup_bonus_min", "0")
                db.set_setting("topup_bonus_amount", "0")
                db.clear_admin_state(chat_id)
                tg.send_message(chat_id, "✅ آفر هدیه‌ی شارژ غیرفعال شد.", reply_markup=admin_menu_keyboard(chat_id))
                return
            db.set_admin_state(chat_id, "await_topup_bonus_amount", None, {"min": value})
            tg.send_message(chat_id, f"مرحله ۲: مبلغ هدیه برای شارژ {value:,} تومان یا بیشتر را بفرستید (تومان)، مثلاً: 50000")
            return
        if value <= 0:
            tg.send_message(chat_id, "مبلغ هدیه باید بیشتر از صفر باشد.")
            return
        mn = int((state.get("data") or {}).get("min", 0))
        db.set_setting("topup_bonus_min", str(mn))
        db.set_setting("topup_bonus_amount", str(value))
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, f"✅ آفر فعال شد: شارژ {mn:,} تومان یا بیشتر ← {value:,} تومان هدیه (فقط خرید کانفیگ).",
                        reply_markup=admin_menu_keyboard(chat_id))
        return

    if action == "await_kyc_threshold":
        cleaned = text.strip().translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789"))
        cleaned = cleaned.replace(",", "").replace("٬", "").replace(" ", "")
        if not cleaned.isdigit():
            tg.send_message(chat_id, "لطفاً فقط یک عدد (تومان) بفرستید، مثلاً: 2000000 (یا 0 برای غیرفعال‌کردن).")
            return
        value = int(cleaned)
        db.set_setting("kyc_threshold", str(value))
        db.clear_admin_state(chat_id)
        if value <= 0:
            tg.send_message(chat_id, "✅ احراز هویت برای مبالغ بالا غیرفعال شد.", reply_markup=admin_menu_keyboard(chat_id))
        else:
            tg.send_message(chat_id, f"✅ حد احراز هویت روی {value:,} تومان تنظیم شد.", reply_markup=admin_menu_keyboard(chat_id))
        return

    if action == "await_category_name":
        data["name"] = text.strip()
        db.set_admin_state(chat_id, "await_category_duration", None, data)
        tg.send_message(
            chat_id,
            "مدت این اشتراک چند روزه است؟ فقط عدد روز بفرستید (مثلاً 30).\n"
            "اگر نمی‌خواهید یادآوری تمدید برای این مدت فعال باشد، عدد 0 بفرستید.",
        )
        return

    if action == "await_category_duration":
        cleaned = text.strip()
        if not cleaned.isdigit():
            tg.send_message(chat_id, "لطفاً فقط عدد بفرستید (روز)، مثلاً: 30 یا 0.")
            return
        duration_days = int(cleaned)
        name = data["name"]
        category_id = db.add_category(name, duration_days=duration_days if duration_days > 0 else None)
        db.clear_admin_state(chat_id)
        duration_line = f"مدت: {duration_days} روز\n" if duration_days > 0 else "یادآوری تمدید: غیرفعال\n"
        tg.send_message(
            chat_id,
            f"✅ مدت اشتراک جدید ساخته شد:\n\nنام: {name}\n{duration_line}شناسه: #{category_id}\n\n"
            f"حالا می‌توانید با دکمه‌ی «افزودن» زیر، پلن داخل این مدت اشتراک بسازید.",
            reply_markup=admin_section_keyboard("plans"),
        )
        return

    if action == "await_edit_category":
        category_id = data.get("category_id")
        cat = db.get_category(category_id)
        if not cat:
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "این مدت اشتراک دیگر پیدا نشد.", reply_markup=admin_section_keyboard("cats"))
            return
        parts = [p.strip() for p in text.split("|")]
        if len(parts) != 2:
            tg.send_message(chat_id, "فرمت درست: نام | مدت به روز\nمثلاً: یک ماهه ویژه | 30\nبرای رد کردن یک فیلد، به‌جایش - بگذارید.")
            return
        new_name = None if parts[0] == "-" else parts[0]
        new_duration = None
        if parts[1] != "-":
            if not parts[1].isdigit():
                tg.send_message(chat_id, "مدت باید عدد باشد (روز) یا - برای رد کردن. دوباره بفرستید:")
                return
            new_duration = int(parts[1])
        db.update_category(category_id, name=new_name, duration_days=new_duration)
        db.clear_admin_state(chat_id)
        updated = db.get_category(category_id)
        tg.send_message(
            chat_id,
            f"✅ مدت اشتراک به‌روزرسانی شد:\n\nنام: {updated['name']}\nمدت: {updated.get('duration_days') or '—'} روز",
            reply_markup=admin_section_keyboard("cats"),
        )
        return

    if action == "await_edit_plan":
        plan_id = data.get("plan_id")
        plan = db.get_plan(plan_id)
        if not plan:
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "این پلن دیگر پیدا نشد.", reply_markup=admin_section_keyboard("plans"))
            return
        parts = [p.strip() for p in text.split("|")]
        if len(parts) != 3:
            tg.send_message(chat_id, "فرمت درست: نام | قیمت | توضیح\nبرای رد کردن یک فیلد، به‌جایش - بگذارید.")
            return
        new_name = None if parts[0] == "-" else parts[0]
        new_price = None
        if parts[1] != "-":
            cleaned = parts[1].replace(",", "").replace("،", "")
            if not cleaned.isdigit():
                tg.send_message(chat_id, "قیمت باید عدد باشد یا - برای رد کردن. دوباره بفرستید:")
                return
            new_price = int(cleaned)
        new_desc = None if parts[2] == "-" else parts[2]
        db.update_plan(plan_id, name=new_name, price=new_price, description=new_desc)
        db.clear_admin_state(chat_id)
        updated = db.get_plan(plan_id)
        tg.send_message(
            chat_id,
            f"✅ پلن به‌روزرسانی شد:\n\nنام: {updated['name']}\nقیمت: {updated['price']:,} تومان\nتوضیح: {updated.get('description') or '—'}",
            reply_markup=admin_section_keyboard("plans"),
        )
        return

    if action == "await_edit_code_value":
        code_id = data.get("code_id")
        code = db.get_discount_code(code_id)
        if not code:
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "این کد تخفیف دیگر پیدا نشد.", reply_markup=admin_section_keyboard("codes"))
            return
        cleaned = text.strip().replace(",", "").replace("،", "")
        if not cleaned.isdigit():
            tg.send_message(chat_id, "لطفاً فقط عدد بفرستید.")
            return
        value = int(cleaned)
        if code["type"] == "percent" and not (1 <= value <= 100):
            tg.send_message(chat_id, "درصد تخفیف باید بین ۱ تا ۱۰۰ باشد. دوباره بفرستید:")
            return
        if code["type"] == "amount" and value <= 0:
            tg.send_message(chat_id, "مبلغ تخفیف باید بزرگ‌تر از صفر باشد. دوباره بفرستید:")
            return
        db.update_discount_code_value(code_id, value)
        db.clear_admin_state(chat_id)
        value_label = f"{value}٪" if code["type"] == "percent" else f"{value:,} تومان"
        tg.send_message(chat_id, f"✅ کد «{code['code']}» به‌روزرسانی شد. مقدار جدید: {value_label}", reply_markup=admin_section_keyboard("codes"))
        return

    if action == "await_edit_uc_factor":
        uc_id = data.get("uc_id")
        uc = db.get_uc_product(uc_id)
        if not uc:
            db.clear_admin_state(chat_id)
            tg.send_message(chat_id, "این آیتم یوسی دیگر پیدا نشد.", reply_markup=admin_section_keyboard("uc"))
            return
        cleaned = text.strip().replace(",", ".")
        try:
            factor = float(cleaned)
            if factor <= 0:
                raise ValueError
        except ValueError:
            tg.send_message(chat_id, "لطفاً یک عدد معتبر و مثبت بفرستید، مثلاً: 0.955")
            return
        db.update_uc_factor(uc_id, factor)
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, f"✅ ضریب «{uc['name']}» به {factor} به‌روزرسانی شد.", reply_markup=admin_section_keyboard("uc"))
        return

    if action == "await_referral_percent":
        cleaned = text.strip().replace(",", ".")
        try:
            percent = float(cleaned)
            if percent < 0 or percent > 100:
                raise ValueError
        except ValueError:
            tg.send_message(chat_id, "لطفاً عددی بین ۰ تا ۱۰۰ بفرستید (0 یعنی غیرفعال).")
            return
        db.set_referral_percent(percent)
        db.clear_admin_state(chat_id)
        if percent <= 0:
            tg.send_message(chat_id, "✅ سیستم رفرال غیرفعال شد.", reply_markup=admin_menu_keyboard(chat_id))
        else:
            tg.send_message(chat_id, f"✅ درصد پورسانت رفرال روی {percent:g}٪ تنظیم شد.", reply_markup=admin_menu_keyboard(chat_id))
        return

    if action == "await_plan_name":
        data["name"] = text.strip()
        db.set_admin_state(chat_id, "await_plan_price", None, data)
        tg.send_message(chat_id, f"نام پلن: «{data['name']}» ثبت شد.\n\nحالا قیمت پلن را فقط با عدد بفرستید (تومان)، مثلاً: 45000")
        return

    if action == "await_plan_price":
        cleaned = text.strip().replace(",", "").replace("،", "")
        if not cleaned.isdigit():
            tg.send_message(chat_id, "لطفاً فقط عدد بفرستید، مثلاً: 45000")
            return
        data["price"] = int(cleaned)
        db.set_admin_state(chat_id, "await_plan_desc", None, data)
        tg.send_message(chat_id, "توضیح کوتاهی برای پلن بفرستید (مثلاً حجم و مدت). اگر نمی‌خواهید توضیح بدهید، فقط - بفرستید.")
        return

    if action == "await_plan_desc":
        description = "" if text.strip() == "-" else text.strip()
        category_id = data.get("category_id")
        plan_id = db.add_plan(data["name"], data["price"], description, category_id=category_id)
        db.clear_admin_state(chat_id)
        category = db.get_category(category_id)
        category_line = f"مدت اشتراک: {category['name']}\n" if category else ""
        tg.send_message(
            chat_id,
            f"✅ پلن جدید ساخته شد:\n\n"
            f"{category_line}"
            f"نام: {data['name']}\n"
            f"قیمت: {data['price']:,} تومان\n"
            f"توضیح: {description or '—'}\n"
            f"شناسه: #{plan_id}",
            reply_markup=admin_section_keyboard("plans"),
        )
        return

    if action == "await_code_text":
        code_text = text.strip()
        if not code_text or " " in code_text or not code_text.replace("-", "").replace("_", "").isalnum():
            tg.send_message(
                chat_id,
                "کد تخفیف باید فقط شامل حروف و عدد باشد (بدون فاصله)، مثلاً: OFF20 یا تخفیف۱۰.\nدوباره بفرستید:",
            )
            return
        if db.get_discount_code_by_text(code_text):
            tg.send_message(chat_id, "این کد تخفیف قبلاً ثبت شده. یک کد دیگر بفرستید:")
            return
        data["code"] = code_text
        db.set_admin_state(chat_id, "await_code_value", None, data)
        if data.get("type") == "percent":
            tg.send_message(chat_id, "مقدار تخفیف را به‌صورت عدد درصد بفرستید (بین ۱ تا ۱۰۰)، مثلاً: 20")
        else:
            tg.send_message(chat_id, "مقدار تخفیف را به تومان و فقط با عدد بفرستید، مثلاً: 50000")
        return

    if action == "await_code_value":
        cleaned = text.strip().replace(",", "").replace("،", "")
        if not cleaned.isdigit():
            tg.send_message(chat_id, "لطفاً فقط عدد بفرستید.")
            return
        value = int(cleaned)
        if data.get("type") == "percent" and not (1 <= value <= 100):
            tg.send_message(chat_id, "درصد تخفیف باید بین ۱ تا ۱۰۰ باشد. دوباره بفرستید:")
            return
        if data.get("type") == "amount" and value <= 0:
            tg.send_message(chat_id, "مبلغ تخفیف باید بزرگ‌تر از صفر باشد. دوباره بفرستید:")
            return
        code_id = db.add_discount_code(data["code"], data["type"], value)
        db.clear_admin_state(chat_id)
        value_label = f"{value}٪" if data.get("type") == "percent" else f"{value:,} تومان"
        tg.send_message(
            chat_id,
            f"✅ کد تخفیف جدید ساخته شد:\n\n"
            f"کد: {data['code']}\n"
            f"مقدار تخفیف: {value_label}\n"
            f"شناسه: #{code_id}",
            reply_markup=admin_section_keyboard("codes"),
        )
        return

    if action == "await_uc_name":
        data["name"] = text.strip()
        db.set_admin_state(chat_id, "await_uc_factor", None, data)
        tg.send_message(
            chat_id,
            f"نام «{data['name']}» ثبت شد.\n\n"
            f"حالا ضریب را بفرستید (عددی که در نرخ لحظه‌ای تتر ضرب می‌شود)، مثلاً: 0.955\n"
            f"(یعنی قیمت این آیتم = نرخ تتر × این عدد، رند به بالا به نزدیک‌ترین هزار تومان)",
        )
        return

    if action == "await_uc_factor":
        cleaned = text.strip().replace(",", ".")
        try:
            factor = float(cleaned)
            if factor <= 0:
                raise ValueError
        except ValueError:
            tg.send_message(chat_id, "لطفاً یک عدد معتبر و مثبت بفرستید، مثلاً: 0.955 یا 4.85")
            return
        uc_id = db.add_uc_product(data["name"], factor)
        db.clear_admin_state(chat_id)
        tg.send_message(
            chat_id,
            f"✅ آیتم یوسی جدید ساخته شد:\n\nنام: {data['name']}\nضریب: {factor}\nشناسه: #{uc_id}",
            reply_markup=admin_section_keyboard("uc"),
        )
        return


def _send_admin_plans_list(chat_id):
    plans = db.list_all_plans()
    if not plans:
        tg.send_message(chat_id, "هیچ پلنی وجود ندارد. برای افزودن، دکمه‌ی «افزودن» را بزنید.", reply_markup=admin_section_keyboard("plans"))
        return
    for p in plans:
        state_label = "🟢 فعال" if p["active"] else "🔴 غیرفعال"
        category = db.get_category(p.get("category_id"))
        category_line = f"⏳ مدت اشتراک: {category['name']}\n" if category else "📦 بدون مدت اشتراک\n"
        caption = f"{category_line}#{p['id']} - {p['name']}\n{p['price']:,} تومان - {state_label}"
        if p.get("description"):
            caption += f"\n{p['description']}"
        tg.send_message(chat_id, caption, reply_markup=plan_row_keyboard(p))
    _send_admin_list_footer(chat_id, "plans")


def _send_admin_uc_list(chat_id):
    products = db.list_all_uc_products()
    if not products:
        tg.send_message(chat_id, "هیچ آیتم یوسی ثبت نشده. برای افزودن، دکمه‌ی «افزودن» را بزنید.", reply_markup=admin_section_keyboard("uc"))
        return
    usdt = get_usdt_price()
    for p in products:
        state_label = "🟢 فعال" if p["active"] else "🔴 غیرفعال"
        if usdt:
            live_price = compute_uc_price(usdt, p["factor"])
            price_line = f"قیمت لحظه‌ای فعلی: {live_price:,} تومان\n"
        else:
            price_line = "⚠️ دریافت قیمت لحظه‌ای الان ناموفق بود\n"
        caption = f"#{p['id']} - {p['name']}\nضریب: {p['factor']}\n{price_line}وضعیت: {state_label}"
        tg.send_message(chat_id, caption, reply_markup=uc_row_keyboard(p))
    _send_admin_list_footer(chat_id, "uc")


def _send_admin_discount_codes_list(chat_id):
    codes = db.list_all_discount_codes()
    if not codes:
        tg.send_message(chat_id, "هیچ کد تخفیفی ثبت نشده. برای افزودن، دکمه‌ی «افزودن» را بزنید.", reply_markup=admin_section_keyboard("codes"))
        return
    for c in codes:
        state_label = "🟢 فعال" if c["active"] else "🔴 غیرفعال"
        value_label = f"{c['value']}٪" if c["type"] == "percent" else f"{c['value']:,} تومان"
        caption = f"🏷 کد: {c['code']}\nنوع تخفیف: {value_label}\nوضعیت: {state_label}"
        tg.send_message(chat_id, caption, reply_markup=discount_code_row_keyboard(c))
    _send_admin_list_footer(chat_id, "codes")


def _send_admin_categories_list(chat_id):
    categories = db.list_all_categories()
    if not categories:
        tg.send_message(chat_id, "هیچ مدت اشتراکی وجود ندارد. برای افزودن، دکمه‌ی «افزودن» را بزنید.", reply_markup=admin_section_keyboard("cats"))
        return
    for c in categories:
        state_label = "🟢 فعال" if c["active"] else "🔴 غیرفعال"
        plan_count = db.count_all_plans_in_category(c["id"])
        caption = f"#{c['id']} - {c['name']}\n{state_label} | تعداد پلن‌ها: {plan_count}"
        tg.send_message(chat_id, caption, reply_markup=category_row_keyboard(c))
    _send_admin_list_footer(chat_id, "cats")


def _send_sales_report(chat_id, message_id=None):
    """گزارش فروش امروز، هفته‌ی اخیر و ماه اخیر را برای ادمین می‌فرستد."""
    now = int(time.time())
    periods = [
        ("امروز", now - 24 * 3600),
        ("۷ روز اخیر", now - 7 * 24 * 3600),
        ("۳۰ روز اخیر", now - 30 * 24 * 3600),
    ]
    parts = [("e", "admin"), " ", ("b", "گزارش فروش"), "\n"]
    for label, start_ts in periods:
        stats = db.get_sales_stats(start_ts, now)
        parts.append(f"\n📅 {label}:\n")
        parts.append(f"تعداد فروش تاییدشده: {stats['count']}\n")
        parts.append(f"جمع فروش: {stats['total']:,} تومان\n")
        if stats["by_type"]:
            type_labels = {"plan": "پلن", "uc": "یوسی"}
            for row in stats["by_type"]:
                label_t = type_labels.get(row["order_type"], row["order_type"])
                parts.append(f"  • {label_t}: {row['cnt']} سفارش، {row['total']:,} تومان\n")
        if stats["top_plan"]:
            parts.append(f"پرفروش‌ترین پلن: {stats['top_plan']['plan_name']} ({stats['top_plan']['cnt']} فروش)\n")
        if stats["topup_count"]:
            parts.append(f"شارژ کیف پول (جدا از فروش): {stats['topup_count']} بار، {stats['topup_total']:,} تومان\n")
    parts.append("\nفقط سفارش‌های تاییدشده حساب می‌شوند؛ سفارش‌های رد، لغو و در انتظار نه.")
    text, entities = build_message(parts)
    show_screen(chat_id, message_id, text, admin_menu_keyboard(chat_id), entities)


def _send_wallet_ledger(chat_id, user_id):
    """۱۵ تراکنش آخر کیف پول یک کاربر + کنترل تطابق جمع دفتر با موجودی فعلی (برای ادمین)."""
    summary = db.get_wallet_ledger_summary(user_id)
    txs = db.list_wallet_transactions(user_id, limit=15)
    if not txs:
        tg.send_message(chat_id, f"برای کاربر {user_id} تراکنشی در دفتر کیف پول ثبت نشده است.")
        return
    ok = (summary["ledger_balance"] == summary["balance"]
          and summary["ledger_referral"] == summary["referral"])
    lines = [
        f"📒 دفتر کیف پول کاربر {user_id}",
        f"موجودی فعلی: {summary['balance']:,} | اعتبار رفرال: {summary['referral']:,}",
        f"جمع دفتر: {summary['ledger_balance']:,} | رفرال: {summary['ledger_referral']:,}",
        "✅ دفتر با موجودی مطابقت دارد." if ok else "⚠️ اختلاف بین دفتر و موجودی فعلی وجود دارد!",
        "",
    ]
    lines += [_format_wallet_tx_line(t) for t in txs]
    tg.send_message(chat_id, "\n".join(lines))


PENDING_PAGE_SIZE = 5


def _approved_card(o):
    """(متن، کیبورد) کارت سفارشِ تاییدشده‌ی منتظر تحویل/واریز."""
    if o.get("order_type") == "uc":
        caption = (
            f"📦 تایید شده، منتظر واریز یوسی - سفارش #{o['id']}\n"
            f"کاربر: {o.get('full_name') or ''} (@{o.get('username') or '-'})\n"
            f"🎯 آیتم: {html.escape(o['plan_name'] or '')} - {o['price']:,} تومان\n"
            f"{uc_total_line(o)}"
            f"{pubg_id_mono_block(o)}\n\n"
            f"پس از واریز دستی UC به اکانت کاربر، دکمه‌ی زیر را بزنید."
        )
        kb = {"inline_keyboard": [
            [btn("واریز انجام شد", f"uc_deliver:{o['id']}", "check")],
            [btn("لغو سفارش", f"cancelorder:{o['id']}", "cross"), btn("پیام به کاربر", f"msguser:{o['user_id']}", "contact")],
        ]}
        return caption, kb
    caption = (
        f"📦 تایید شده، منتظر فایل کانفیگ - سفارش #{o['id']}\n"
        f"کاربر: {o.get('full_name') or ''} (@{o.get('username') or '-'})\n"
        f"پلن: {html.escape(o['plan_name'] or '')} - {o['price']:,} تومان"
    )
    if o.get("pubg_info"):
        caption += f"\n🎮 اطلاعات پابجی: {html.escape(o['pubg_info'])}"
    kb = {"inline_keyboard": [
        [{"text": "📎 آماده‌ام فایل بفرستم", "callback_data": f"resume:{o['id']}"}],
        [btn("لغو سفارش", f"cancelorder:{o['id']}", "cross"), btn("پیام به کاربر", f"msguser:{o['user_id']}", "contact")],
    ]}
    return caption, kb


def _review_caption(o):
    price_line = f"پلن: {o['plan_name']} - {o['price']:,} تومان"
    if o.get("order_type") == "wallet_topup":
        price_line = f"👛 شارژ کیف پول - مبلغ: {o['price']:,} تومان"
        if topup_bonus_for(o["price"]):
            price_line += f"\n🎁 هدیه‌ی شارژ در صورت تایید: {topup_bonus_for(o['price']):,} تومان"
    elif o.get("discount_code"):
        price_line = (
            f"پلن: {o['plan_name']}\n"
            f"قیمت اصلی: {o.get('original_price') or o['price']:,} تومان\n"
            f"کد تخفیف: {o['discount_code']} (-{o.get('discount_amount') or 0:,} تومان)\n"
            f"مبلغ نهایی: {o['price']:,} تومان"
        )
    caption = (
        f"🧾 در انتظار بررسی رسید - سفارش #{o['id']}\n"
        f"کاربر: {o.get('full_name') or ''} (@{o.get('username') or '-'})\n"
        f"{price_line}"
    )
    if o.get("pubg_info"):
        caption += f"\n🎮 اطلاعات پابجی: {o['pubg_info']}"
    elif o.get("order_type") == "uc":
        caption += "\n⚠️ آیدی عددی پابجی هنوز از کاربر دریافت نشده"
    return caption


def _send_pending_item(chat_id, kind, o):
    if kind == "review":
        if o.get("kyc_required"):
            _send_kyc_files_to_admin(chat_id, o)
        _send_review_card(chat_id, o, _review_caption(o), admin_review_keyboard(o))
    elif kind == "approved":
        caption, kb = _approved_card(o)
        tg.send_message(chat_id, caption, reply_markup=kb)
    else:
        tg.send_message(
            chat_id,
            f"👤 {html.escape(o.get('full_name') or '-')} (@{html.escape(o.get('username') or '-')}) | آیدی: <code>{o['user_id']}</code>\n"
            + _admin_order_caption(o),
            reply_markup={"inline_keyboard": [[
                btn("لغو سفارش", f"cancelorder:{o['id']}", "cross"),
                btn("پیام به کاربر", f"msguser:{o['user_id']}", "contact"),
            ]]},
        )


def _send_pending_orders(chat_id, page=0):
    """لیست سفارش‌های نیازمند اقدام، ۵ تا ۵ تا (با دکمه‌ی صفحه‌ی بعد/قبل)."""
    orders = db.list_pending_orders()
    approved = db.list_approved_awaiting_delivery()
    unpaid, unpaid_total = db.list_unpaid_orders(30)
    if not orders and not approved and not unpaid:
        tg.send_message(chat_id, "سفارش در انتظار بررسی، ارسال یا پرداخت وجود ندارد.")
        return
    items = [("review", o) for o in orders] + [("approved", o) for o in approved] + [("unpaid", o) for o in unpaid]
    pages = max(1, math.ceil(len(items) / PENDING_PAGE_SIZE))
    page = min(max(0, page), pages - 1)
    if page == 0:
        tg.send_message(
            chat_id,
            f"🧾 در انتظار بررسی رسید: {len(orders)} | منتظر تحویل/واریز: {len(approved)} | "
            f"در انتظار پرداخت: {unpaid_total}",
        )
    for kind, o in items[page * PENDING_PAGE_SIZE:(page + 1) * PENDING_PAGE_SIZE]:
        _send_pending_item(chat_id, kind, o)
    if pages > 1:
        nav = []
        if page > 0:
            nav.append(btn("صفحه‌ی قبل", f"pending_page:{page - 1}", "back"))
        if page < pages - 1:
            nav.append(btn("صفحه‌ی بعد", f"pending_page:{page + 1}", "pending"))
        tg.send_message(chat_id, f"صفحه {page + 1} از {pages} ({len(items)} مورد)", reply_markup={"inline_keyboard": [nav]})


def _send_order_card(chat_id, order_id):
    """/order <شماره>: کارت کامل یک سفارش با دکمه‌های مناسب وضعیتش."""
    o = db.get_order(order_id)
    if not o:
        tg.send_message(chat_id, "سفارشی با این شماره پیدا نشد.")
        return
    status = o["status"]
    caption = (f"👤 {o.get('full_name') or '-'} (@{o.get('username') or '-'}) | آیدی: {o['user_id']}\n"
               + _admin_order_caption(o))
    if o.get("kyc_required"):
        caption += "\n🪪 این سفارش احراز هویت دارد."
    extra = [btn("پیام به کاربر", f"msguser:{o['user_id']}", "contact"), btn("پروفایل", f"admin_wuser:{o['user_id']}:n", "users")]
    if status == "awaiting_review":
        kb = admin_review_keyboard(o)
    elif status == "approved":
        kb = _approved_card(o)[1]
    elif status == "pending_receipt":
        kb = {"inline_keyboard": [[btn("لغو سفارش", f"cancelorder:{o['id']}", "cross")], extra]}
    else:
        kb = {"inline_keyboard": [extra]}
    if o.get("kyc_required") and status in ("awaiting_review", "awaiting_kyc"):
        _send_kyc_files_to_admin(chat_id, o)
    _send_review_card(chat_id, o, caption, kb)


# ---------------------------------------------------------------------------
# کد تخفیف و ثبت سفارش
# ---------------------------------------------------------------------------

def _compute_discount(plan_price, code_row):
    """قیمت نهایی و مبلغ تخفیف را بر اساس نوع کد محاسبه می‌کند."""
    if code_row["type"] == "percent":
        discount_amount = round(plan_price * code_row["value"] / 100)
    else:
        discount_amount = code_row["value"]
    discount_amount = min(discount_amount, plan_price)  # تخفیف نمی‌تواند بیشتر از قیمت پلن باشد
    final_price = max(plan_price - discount_amount, 0)
    return final_price, discount_amount


def _create_order_and_show_payment(chat_id, from_user, plan_id, code_row=None, message_id=None):
    """سفارش را (با یا بدون کد تخفیف) می‌سازد و پیام اطلاعات پرداخت را می‌فرستد."""
    if is_sales_paused("gaming"):
        tg.send_message(chat_id, sales_paused_text("gaming"))
        return
    plan = db.get_plan(plan_id)
    if not plan or not plan["active"]:
        tg.send_message(chat_id, "این پلن دیگر موجود نیست.")
        return

    original_price = plan["price"]
    if code_row:
        final_price, discount_amount = _compute_discount(original_price, code_row)
        discount_code_text = code_row["code"]
    else:
        final_price, discount_amount = original_price, 0
        discount_code_text = None

    order_id = db.create_order(
        user_id=from_user["id"],
        username=from_user.get("username"),
        full_name=(from_user.get("first_name", "") + " " + from_user.get("last_name", "")).strip(),
        plan_id=plan["id"],
        plan_name=plan["name"],
        price=final_price,
        original_price=original_price,
        discount_code=discount_code_text,
        discount_amount=discount_amount,
    )

    if discount_amount > 0:
        price_parts = [
            f"قیمت پلن: {original_price:,} تومان\n",
            ("e", "discount"), f" کد تخفیف: {discount_code_text} (-{discount_amount:,} تومان)\n",
            "مبلغ قابل پرداخت: ", ("b", f"{final_price:,} تومان"), "\n\n",
        ]
    else:
        price_parts = [f"قیمت: {final_price:,} تومان\n\n"]

    parts = [
        ("e", "gaming"), f" سفارش شما ثبت شد (شماره سفارش: {order_id})\n\n",
        f"پلن: {plan['name']}\n",
        *price_parts,
        "لطفاً روش پرداخت را انتخاب کنید:",
    ]
    show_rich(chat_id, message_id, parts, payment_method_keyboard(order_id))


# ---------------------------------------------------------------------------
# سبد خرید یوسی
# ---------------------------------------------------------------------------

def _cart_units(user_id):
    """مجموع تعداد آیتم‌های سبد خرید یوسی کاربر (برای نمایش روی دکمه‌ی «سبد خرید»)."""
    try:
        return sum(r["quantity"] for r in db.cart_get(user_id))
    except Exception:
        return 0


def _cart_add(user_id, uc_id, quantity):
    """quantity عدد به آیتم داخل سبد اضافه می‌کند (حداکثر UC_MAX_QUANTITY برای هر آیتم).
    اگر چیزی اضافه نشد چون به سقف رسیده، False برمی‌گرداند."""
    current = db.cart_get_quantity(user_id, uc_id)
    new_qty = min(current + quantity, UC_MAX_QUANTITY)
    if new_qty <= current:
        return False
    db.cart_set_quantity(user_id, uc_id, new_qty)
    return True


def _cart_lines(user_id):
    """[(آیتم یوسی, تعداد)] فقط برای آیتم‌های فعال. آیتم‌هایی که ادمین غیرفعال/حذف کرده از سبد
    پاک می‌شوند و نام غیرفعال‌شده‌ها در خروجی دوم می‌آید تا به کاربر اطلاع بدهیم."""
    lines, dropped = [], []
    for row in db.cart_get(user_id):
        uc = db.get_uc_product(row["uc_id"])
        if not uc or not uc["active"]:
            db.cart_set_quantity(user_id, row["uc_id"], 0)
            if uc:
                dropped.append(uc["name"])
            continue
        qty = row["quantity"]
        if qty > UC_MAX_QUANTITY:  # سبدهای قدیمی که قبل از کاهش سقف ساخته شده‌اند
            qty = UC_MAX_QUANTITY
            db.cart_set_quantity(user_id, row["uc_id"], qty)
        lines.append((uc, qty))
    return lines, dropped


def _order_name_from_lines(lines):
    """نام سفارش یوسی از روی آیتم‌های سبد، مثلاً: «60 UC × 2 + 325 UC»."""
    return " + ".join(uc["name"] if qty == 1 else f"{uc['name']} × {qty}" for uc, qty in lines)


def _show_uc_cart(chat_id, message_id=None):
    """سبد خرید یوسی کاربر را (با قیمت لحظه‌ای) نشان می‌دهد؛ اگر message_id باشد همان پیام را ویرایش می‌کند."""
    lines, dropped = _cart_lines(chat_id)
    dropped_note = ""
    if dropped:
        dropped_note = ("\n\n⚠️ این آیتم‌ها دیگر موجود نیستند و از سبد حذف شدند: "
                        + "، ".join(html.escape(n) for n in dropped))

    if not lines:
        text = "🛍 سبد خرید یوسی شما خالی است." + dropped_note
        kb = {"inline_keyboard": [[btn("لیست قیمت یوسی", "maincat:uc", "pricelist")], nav_row("shop_menu")]}
    else:
        usdt = get_usdt_price()
        rows_text = []
        total = 0
        uc_sum, uc_sum_ok = 0, True
        for uc, qty in lines:
            amt = uc_amount(uc)
            if amt is None:
                uc_sum_ok = False
            else:
                uc_sum += amt * qty
            label = f"▫️ {html.escape(uc['name'])} × {qty}"
            if usdt is not None:
                line_price = compute_uc_price(usdt, uc["factor"]) * qty
                total += line_price
                label += f" = {line_price:,} تومان"
            rows_text.append(label)
        text = "🛍 <b>سبد خرید یوسی شما</b>\n\n" + "\n".join(rows_text) + "\n\n"
        if uc_sum_ok and uc_sum:
            text += f"🎯 مجموع یوسی: <b>{uc_sum:,} UC</b>\n"
        if usdt is not None:
            text += f"💰 مبلغ قابل پرداخت: <b>{total:,} تومان</b>\n(قیمت‌ها لحظه‌ای‌اند و هنگام پرداخت نهایی دوباره محاسبه می‌شوند)"
        else:
            text += "⚠️ قیمت لحظه‌ای تتر فعلاً در دسترس نیست؛ قیمت نهایی هنگام پرداخت محاسبه می‌شود."
        text += (
            "\n\n✅ همین مقدار را می‌خواهید؟ «ثبت سفارش و پرداخت» را بزنید.\n"
            "➕ بسته‌ی یوسی دیگری هم لازم دارید؟ «افزودن بسته‌ی یوسی دیگر» را بزنید.\n"
            "✏️ برای تغییر تعداد از «کم» و «زیاد» استفاده کنید."
        )
        text += dropped_note

        rows = []
        for uc, qty in lines:
            rows.append([{"text": f"{uc['name']} × {qty}", "callback_data": "noop"}])
            rows.append([
                btn("کم", f"cartq:{uc['id']}:-1", "minus"),
                btn("زیاد", f"cartq:{uc['id']}:1", "plus"),
                btn("حذف", f"cartdel:{uc['id']}", "trash"),
            ])
        rows.append([btn("ثبت سفارش و پرداخت", "cartcheckout", "check")])
        rows.append([btn("افزودن بسته‌ی یوسی دیگر", "maincat:uc", "plus")])
        rows.append([btn("خالی کردن سبد", "cartclear", "trash")])
        rows.append(nav_row("maincat:uc"))
        kb = {"inline_keyboard": rows}

    if message_id:
        tg.edit_message_text(chat_id, message_id, text, reply_markup=kb)
    else:
        tg.send_message(chat_id, text, reply_markup=kb)


def _checkout_uc_cart(chat_id, from_user, message_id=None):
    """قیمت را دوباره (لحظه‌ای) استعلام می‌گیرد، از همه‌ی آیتم‌های سبد یک سفارش یوسی می‌سازد،
    سبد را خالی می‌کند و پیام پرداخت را می‌فرستد."""
    if is_sales_paused("uc"):
        tg.send_message(chat_id, sales_paused_text("uc"))
        return
    lines, dropped = _cart_lines(from_user["id"])
    if not lines:
        tg.send_message(chat_id, "🛍 سبد خرید شما خالی است. از «لیست قیمت یوسی» آیتم اضافه کنید.")
        return

    # استعلام مجدد قیمت لحظه‌ای — چون ممکن است کاربر مدتی بعد از دیدن سبد، پرداخت را بزند
    usdt = get_usdt_price()
    if usdt is None:
        tg.send_message(
            chat_id,
            "⚠️ در حال حاضر امکان دریافت قیمت لحظه‌ای تتر وجود ندارد. لطفاً چند لحظه‌ی دیگر دوباره تلاش کنید.",
        )
        return

    priced = [(uc, qty, compute_uc_price(usdt, uc["factor"])) for uc, qty in lines]
    final_price = sum(unit * qty for _, qty, unit in priced)
    order_name = _order_name_from_lines(lines)

    order_id = db.create_order(
        user_id=from_user["id"],
        username=from_user.get("username"),
        full_name=(from_user.get("first_name", "") + " " + from_user.get("last_name", "")).strip(),
        plan_id=lines[0][0]["id"] if len(lines) == 1 else None,
        plan_name=order_name,
        price=final_price,
        order_type="uc",
        usdt_rate=usdt,
    )
    db.cart_clear(from_user["id"])

    parts = [("e", "uc"), f" سفارش شما ثبت شد (شماره سفارش: {order_id})\n\n", "🛍 آیتم‌ها:\n"]
    for uc, qty, unit in priced:
        qty_txt = f" × {qty}" if qty > 1 else ""
        parts.append(f"• {uc['name']}{qty_txt} = {unit * qty:,} تومان\n")
    total_uc = uc_total_from_name(order_name)
    if total_uc:
        parts.append(f"\n🎯 مجموع یوسی: {total_uc:,} UC\n")
    parts += [
        f"\nنرخ تتر لحظه‌ای: {usdt:,.0f} تومان\n",
        "قیمت قابل پرداخت: ", ("b", f"{final_price:,} تومان"), "\n\n",
        ("e", "clock"), f" این قیمت فقط تا {UC_ORDER_EXPIRY_SECONDS // 60} دقیقه معتبر است.\n\n",
        ("e", "warning"), " ", ("b", "توجه مهم:"), " بعد از پرداخت، حتماً ",
        ("b", "آیدی عددی پابجی"), " خود را برای ما بفرستید. ",
        ("b", "اگر آیدی عددی ارسال نشود یا ناقص باشد، سفارش انجام نمی‌شود."), "\n\n",
        "لطفاً روش پرداخت را انتخاب کنید:",
    ]
    show_rich(chat_id, message_id, parts, payment_method_keyboard(order_id))


def _show_card_payment_info(chat_id, order, message_id=None):
    """اطلاعات پرداخت کارت‌به‌کارت را برای یک سفارش (پلن یا یوسی) نمایش می‌دهد."""
    label = "آیتم" if order.get("order_type") == "uc" else "پلن"
    parts = [
        ("e", "card"), f" پرداخت کارت به کارت — سفارش #{order['id']}\n\n",
        f"{label}: {order['plan_name']}\n",
        f"مبلغ قابل پرداخت: {order['price']:,} تومان\n\n",
    ]
    if order.get("order_type") == "uc":
        parts.append(
            "⏰ توجه: این قیمت فقط تا ۱۰ دقیقه پس از ثبت سفارش معتبر است. اگر دیرتر واریز کنید، "
            "سفارش منقضی می‌شود و رسید تایید نخواهد شد؛ در این صورت باید دوباره از منو استعلام قیمت بگیرید.\n\n"
        )
    else:
        parts.append(f"⏰ مهلت پرداخت این سفارش {UNPAID_ORDER_EXPIRY_SECONDS // 60} دقیقه است.\n\n")
    parts.append("لطفاً مبلغ فوق را به شماره کارت زیر واریز کنید:\n")
    parts.append(("code", config.CARD_NUMBER))
    parts.append(f"\nبه نام: {config.CARD_HOLDER}\n\n")
    parts.append("سپس عکس رسید پرداخت را همینجا برای من ارسال کنید.\n")
    if order.get("order_type") == "uc":
        parts += [
            "\n🚨 ", ("b", "بعد از ارسال رسید، حتماً آیدی عددی پابجی خود را هم بفرستید!"), "\n",
            "⛔️ اگر آیدی عددی ارسال نشود یا ناقص باشد، سفارش انجام نمی‌شود.\n\n",
        ]
    parts.append("اگر منصرف شدید، دکمه‌ی «لغو سفارش» را بزنید.")
    shown = show_rich(chat_id, message_id, parts, cancel_order_keyboard(order["id"]))
    db.set_order_msg(order["id"], "pay_msg_id", shown)


def _pay_with_wallet(chat_id, from_user, order, message_id=None):
    """پرداخت یک سفارش (پلن یا یوسی) از موجودی کیف پول کاربر.
    نکته: اعتبار رفرال فقط برای خرید پلن/کانفیگ قابل استفاده است، نه خرید یوسی؛
    پس برای سفارش‌های یوسی فقط موجودی آزاد کیف پول در نظر گرفته می‌شود."""
    order_id = order["id"]
    price = order["price"]
    is_plan = order.get("order_type") not in ("uc",)

    if is_plan:
        available = db.get_combined_plan_balance(chat_id)
    else:
        available = db.get_user_balance(chat_id)

    if available < price:
        shortfall = price - available
        text = (
            f"❌ موجودی کافی نیست.\n\n"
            f"موجودی قابل استفاده: {available:,} تومان\n"
            f"مبلغ سفارش: {price:,} تومان\n"
            f"کسری: {shortfall:,} تومان\n\n"
            f"می‌توانید کیف پول را شارژ کنید یا از طریق کارت به کارت پرداخت کنید."
        )
        kb = {
            "inline_keyboard": [
                [btn("شارژ کیف پول", "wallet_menu", "wallet")],
                [btn("پرداخت کارت به کارت", f"pay_card:{order_id}", "card")],
                cancel_order_row(order_id),
            ]
        }
        show_screen(chat_id, message_id, text, kb)
        return

    # قفل اتمیک: اگر هم‌زمان دو درخواست (دو بار زدن دکمه‌ی پرداخت، یا پرداخت + لغو) برسد،
    # فقط یکی از آن‌ها اجازه‌ی ادامه دارد و مبلغ دوبار از کیف پول کسر نمی‌شود.
    if not db.claim_order(order_id, "pending_receipt", "approved"):
        return

    try:
        if is_plan:
            new_balance, new_referral = db.spend_for_plan_purchase(chat_id, price, order_id=order_id)
            balance_line = f"موجودی کیف پول: {new_balance:,} تومان"
            if new_referral > 0:
                balance_line += f"\nاعتبار رفرال باقی‌مانده: {new_referral:,} تومان"
        else:
            new_balance = db.add_user_balance(chat_id, -price, kind="purchase", order_id=order_id)
            balance_line = f"موجودی جدید: {new_balance:,} تومان"
    except Exception:
        # اگر کسر موجودی خطا داد، سفارش را به حالت قبل برگردان تا بدون پرداخت «تایید شده» نماند
        db.set_order_status(order_id, "pending_receipt")
        raise

    db.set_order_payment_method(order_id, "wallet")

    show_screen(
        chat_id, message_id,
        f"✅ مبلغ {price:,} تومان از موجودی شما کسر شد.\n"
        f"{balance_line}\n\n"
        f"سفارش شما ثبت شد و به‌زودی برای شما ارسال/واریز می‌شود.",
        {"inline_keyboard": [[btn("منوی اصلی", "back_main", "home")]]},
    )

    buyer_line = (
        f"کاربر: {order.get('full_name') or ''} (@{order.get('username') or '-'}) | آیدی: {order['user_id']}"
    )

    if order.get("order_type") == "uc":
        uc_text = (
            f"👛 سفارش #{order_id} با موجودی کیف پول پرداخت شد.\n"
            f"🎯 آیتم: {order['plan_name']}\n"
            f"مبلغ: {price:,} تومان\n"
            f"{buyer_line}\n\n"
            f"پس از واریز دستی UC به اکانت کاربر، دکمه‌ی زیر را بزنید."
        )
        uc_kb = {"inline_keyboard": [[btn("واریز انجام شد", f"uc_deliver:{order_id}", "check")]]}
        for admin_id in ADMIN_IDS:
            _send_review_card(admin_id, {"id": order_id}, uc_text, uc_kb)
        _ask_pubg_info(chat_id, order_id)
    else:
        plan_text = (
            f"👛 سفارش #{order_id} با موجودی کیف پول پرداخت شد.\n"
            f"پلن: {order['plan_name']}\n"
            f"مبلغ: {price:,} تومان\n"
            f"{buyer_line}\n\n"
            f"لطفاً فایل(های) کانفیگ را ارسال کنید."
        )
        plan_kb = {"inline_keyboard": [[{"text": "📎 آماده‌ام فایل بفرستم", "callback_data": f"resume:{order_id}"}]]}
        for admin_id in ADMIN_IDS:
            _send_review_card(admin_id, {"id": order_id}, plan_text, plan_kb)


def _create_wallet_topup_order_and_show_payment(chat_id, from_user, amount):
    """سفارش شارژ کیف پول را می‌سازد و اطلاعات پرداخت کارت‌به‌کارت را نشان می‌دهد."""
    order_id = db.create_order(
        user_id=from_user["id"],
        username=from_user.get("username"),
        full_name=(from_user.get("first_name", "") + " " + from_user.get("last_name", "")).strip(),
        plan_id=None,
        plan_name="شارژ کیف پول",
        price=amount,
        order_type="wallet_topup",
    )

    parts = [
        ("e", "wallet"), f" درخواست شارژ کیف پول ثبت شد (شماره سفارش: {order_id})\n\n",
        "مبلغ: ", ("b", f"{amount:,} تومان"), "\n",
    ] + ([f"🎁 هدیه‌ی شارژ بعد از تایید: {topup_bonus_for(amount):,} تومان (فقط خرید پلن/کانفیگ)\n"] if topup_bonus_for(amount) else []) + [
        "\n",
        "لطفاً مبلغ فوق را به شماره کارت زیر واریز کنید:\n",
        ("code", config.CARD_NUMBER),
        f"\nبه نام: {config.CARD_HOLDER}\n\n",
        f"⏰ مهلت پرداخت این سفارش {UNPAID_ORDER_EXPIRY_SECONDS // 60} دقیقه است.\n",
        "سپس عکس رسید پرداخت را همینجا برای من ارسال کنید تا کیف پول شما شارژ شود.\n",
        "اگر منصرف شدید، دکمه‌ی «لغو سفارش» را بزنید.",
    ]
    text, entities = build_message(parts)
    resp = tg.send_message(chat_id, text, reply_markup=cancel_order_keyboard(order_id), entities=entities)
    db.set_order_msg(order_id, "pay_msg_id", _msg_id(resp))


def _handle_wallet_topup_amount_entry(chat_id, text, from_user):
    """مبلغی که کاربر برای شارژ کیف پول فرستاده را اعتبارسنجی و سفارش را می‌سازد."""
    cleaned = text.replace(",", "").replace("،", "").strip()
    if not cleaned.isdigit():
        _prompt_error(
            chat_id,
            "❌ لطفاً مبلغ را فقط به‌صورت عدد (تومان) ارسال کنید. مثلاً: 100000\n"
            "(برای انصراف، /cancel را بزنید)",
        )
        return

    amount = int(cleaned)
    if amount < 10000:
        _prompt_error(
            chat_id,
            "❌ حداقل مبلغ شارژ کیف پول ۱۰,۰۰۰ تومان است. لطفاً مبلغ را دوباره ارسال کنید.\n"
            "(برای انصراف، /cancel را بزنید)",
        )
        return

    _drop_prompts(chat_id)  # پیام «مبلغ را بفرستید» و خطاهای احتمالی بعد از پاسخ مشتری پاک می‌شوند
    db.clear_admin_state(chat_id)
    _create_wallet_topup_order_and_show_payment(chat_id, from_user, amount)


def _handle_ticket_user_message(chat_id, text, state, from_user, msg_id=None, media=None):
    """پیام کاربر داخل یک تیکت پشتیبانی را ثبت و برای ادمین‌ها ارسال می‌کند."""
    ticket_id = (state.get("data") or {}).get("ticket_id")
    ticket = db.get_ticket(ticket_id) if ticket_id else None
    if not ticket or ticket["status"] != "open":
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, "این گفتگوی پشتیبانی دیگر باز نیست. برای شروع تیکت جدید، از «ارتباط با ما» اقدام کنید.")
        return

    first_message = not db.ticket_has_user_message(ticket_id)
    _drop_prompts(chat_id, state)
    logged = text
    if media:
        logged = f"[{TICKET_MEDIA_LABELS[media[0]]}] {media[2]}".strip()
    db.add_ticket_message(ticket_id, "user", logged)
    # نگه‌داشتن وضعیت تا کاربر بتواند پیام‌های بعدی را هم بدون فشردن دوباره‌ی دکمه بفرستد
    db.set_admin_state(chat_id, "await_ticket_message", None, {"ticket_id": ticket_id})

    name = (from_user.get("first_name", "") + " " + from_user.get("last_name", "")).strip() or "بدون‌نام"
    username_line = f"@{from_user.get('username')}" if from_user.get("username") else "—"
    admin_text, admin_entities = build_message([
        ("e", "contact"), f" پیام جدید در تیکت #{ticket_id}\n\n",
        f"از: {name} ({username_line}) | آیدی: {chat_id}\n\n",
        f"«{logged}»",
    ])
    kb = {
        "inline_keyboard": [
            [
                btn("پاسخ", f"ticket_reply_start:{ticket_id}", "reply"),
                btn("بستن تیکت", f"ticket_close:{ticket_id}", "lock"),
            ],
            [btn("پروفایل", f"admin_wuser:{chat_id}:n", "users")],
        ]
    }
    for admin_id in ADMIN_IDS:
        if media and len(admin_text) <= tg.CAPTION_LIMIT:
            resp = tg.send_media_by_file_id(admin_id, media[0], media[1], caption=admin_text,
                                            reply_markup=kb, caption_entities=admin_entities)
            if resp and resp.get("ok"):
                continue
        tg.send_message(admin_id, admin_text, reply_markup=kb, entities=admin_entities)
        if media:
            tg.send_media_by_file_id(admin_id, media[0], media[1])

    if not first_message:
        # پیام‌های بعدیِ همان تیکت: به‌جای پیام تاییدیه‌ی جدا، فقط روی پیام کاربر 👍 می‌گذاریم
        _ack_message(chat_id, msg_id, "✅ پیام شما ارسال شد.")
        return
    # عمداً هیچ دکمه‌ای زیر این پیام نیست تا کاربر اشتباهی تیکت را نبندد
    confirm_text, confirm_entities = build_message([
        ("e", "check"), " پیام شما برای پشتیبانی ارسال شد.\n",
        "⏳ لطفاً منتظر پاسخ بمانید؛ جواب همین‌جا می‌آید. اگر چیز دیگری یادتان افتاد، همین‌جا بفرستید.",
    ])
    tg.send_message(chat_id, confirm_text, entities=confirm_entities)


def _handle_support_message_to_user(admin_chat_id, text, state, from_user=None, reply_msg_id=None):
    """ادمین از طریق ربات به یک کاربر پیام می‌دهد: همان سیستم تیکت (تیکت باز کاربر یا یک تیکت جدید)."""
    uid = (state.get("data") or {}).get("uid")
    if not uid or not db.get_user_row(uid):
        db.clear_admin_state(admin_chat_id)
        tg.send_message(admin_chat_id, "کاربر پیدا نشد.")
        return
    ticket = db.get_open_ticket_for_user(uid)
    ticket_id = ticket["id"] if ticket else db.create_ticket(uid)
    _handle_ticket_admin_reply(admin_chat_id, text, {"data": {"ticket_id": ticket_id}}, from_user, reply_msg_id)


def _after_order_cancelled(order, admin_id, refunded=0, cq=None):
    """بعد از لغو سفارش توسط ادمین: اطلاع به کاربر، گزارش به ادمین و اطلاع به ادمین‌های دیگر."""
    order_id = order["id"]
    item = order.get("plan_name") or "شارژ کیف پول"
    user_text = f"❌ سفارش #{order_id} ({html.escape(item)}) توسط پشتیبانی لغو شد."
    if refunded:
        user_text += f"\n💰 مبلغ {refunded:,} تومان به کیف پول شما برگشت داده شد."
    user_text += "\nاگر سوالی دارید از «ارتباط با ما» اقدام کنید."
    _clear_wait_message(order)
    try:
        resp = tg.send_message(order["user_id"], user_text)
        notified = bool(resp and resp.get("ok"))
    except Exception:
        logger.exception("اطلاع لغو سفارش به کاربر ناموفق بود")
        notified = False
    admin_text = "🚫 لغو شد"
    if refunded:
        admin_text += f" (💰 {refunded:,} تومان به کیف پول کاربر برگشت)"
    if not notified:
        admin_text += "\n⚠️ پیام اطلاع‌رسانی به کاربر ارسال نشد (احتمالاً ربات را بلاک کرده)."
    if cq:
        _close_clicked_message(cq, order_id, admin_text)
    else:
        tg.send_message(admin_id, f"سفارش #{order_id}: {admin_text}")
    _notify_other_admins(order_id, admin_id, "🚫 لغو شد" + (" (با برگشت وجه به کیف پول)" if refunded else ""),
                         cq["from"] if cq else None, final=True)


def _handle_admin_cancel_order(cq, data):
    cq_id = cq["id"]
    admin_id = cq["from"]["id"]
    if not is_admin(admin_id):
        tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
        return
    parts = data.split(":")
    try:
        order_id = int(parts[1])
    except (IndexError, ValueError):
        tg.answer_callback_query(cq_id)
        return
    order = db.get_order(order_id)
    if not order:
        tg.answer_callback_query(cq_id, "سفارش پیدا نشد.", show_alert=True)
        return
    status = order["status"]

    if parts[0] == "cancelorder":
        if status == "pending_receipt":
            # هنوز پرداختی/رسیدی نیست: مستقیم لغو می‌شود
            if not db.claim_order(order_id, "pending_receipt", "cancelled"):
                tg.answer_callback_query(cq_id, "وضعیت این سفارش همین الان تغییر کرد.", show_alert=True)
                return
            tg.answer_callback_query(cq_id, "لغو شد.")
            _after_order_cancelled(order, admin_id, cq=cq)
            return
        if status == "approved":
            # تاییدشده و منتظر تحویل (مثلاً با کیف پول پرداخت شده): اول تکلیف پول را می‌پرسیم
            tg.answer_callback_query(cq_id)
            how = "کیف پول" if order.get("payment_method") == "wallet" else "کارت‌به‌کارت"
            tg.send_message(
                admin_id,
                f"⚠️ لغو سفارش #{order_id} ({html.escape(order.get('plan_name') or '-')}) — {order['price']:,} تومان\n"
                f"روش پرداخت: {how}\n\n"
                f"مبلغ سفارش به کیف پول کاربر برگردانده شود؟",
                reply_markup={"inline_keyboard": [
                    [btn(f"لغو و برگشت {order['price']:,} تومان به کیف پول", f"cancelrf:{order_id}:1", "check")],
                    [btn("لغو بدون برگشت وجه", f"cancelrf:{order_id}:0", "cross")],
                    [btn("انصراف", "cancelorder_no", "cross")],
                ]},
            )
            return
        label = ORDER_STATUS_LABELS.get(status, status)
        tg.answer_callback_query(cq_id, f"این سفارش در وضعیت «{label}» است و قابل لغو نیست.", show_alert=True)
        return

    # cancelrf:<id>:<0|1>
    refund = len(parts) > 2 and parts[2] == "1"
    if status != "approved" or not db.claim_order(order_id, "approved", "cancelled"):
        tg.answer_callback_query(cq_id, "این سفارش دیگر در وضعیت قابل لغو نیست (احتمالاً قبلاً رسیدگی شده).", show_alert=True)
        return
    refunded = 0
    if refund and order["price"] > 0:
        db.refund_order_to_wallet(
            order["user_id"], order_id, order["price"],
            note=f"برگشت وجه سفارش لغوشده #{order_id} (ادمین {admin_id})",
        )
        refunded = order["price"]
    # اگر ادمین وسط ارسال فایل همین سفارش بود، آن حالت را ببندیم
    st = db.get_admin_state(admin_id)
    if st and st.get("order_id") == order_id:
        db.clear_admin_state(admin_id)
    tg.answer_callback_query(cq_id, "لغو شد.")
    _after_order_cancelled(order, admin_id, refunded, cq=cq)


def _ticket_user_message(ticket_id, body):
    """(parts, keyboard) پیام پشتیبانی برای کاربر. body می‌تواند خالی باشد (مدیای بدون کپشن)."""
    if db.ticket_has_user_message(ticket_id):
        # مشتری خودش تیکت زده → پاسخ پشتیبانی با گزینه‌ی ادامه / بستن
        parts = [("e", "contact"), " ", ("b", "پاسخ پشتیبانی:")]
        if body:
            parts += ["\n\n", body]
        parts += ["\n\n", "—\nاگر مشکلتان حل شد، تیکت را ببندید؛ اگر نه «ادامه گفتگو» را بزنید."]
        return parts, ticket_after_reply_keyboard(ticket_id)
    # ادمین گفتگو را شروع کرده (مشتری تیکت نزده) → فقط «پیام از پشتیبانی» و دکمه‌ی پاسخ
    parts = [("e", "contact"), " ", ("b", "پیام از پشتیبانی")]
    if body:
        parts += ["\n\n", body]
    return parts, ticket_admin_initiated_keyboard(ticket_id)


def _handle_ticket_admin_reply(admin_chat_id, text, state, from_user=None, reply_msg_id=None):
    """پاسخ متنیِ ادمین به یک تیکت پشتیبانی را ثبت و برای کاربر ارسال می‌کند."""
    ticket_id = (state.get("data") or {}).get("ticket_id")
    ticket = db.get_ticket(ticket_id) if ticket_id else None
    if not ticket or ticket["status"] != "open":
        db.clear_admin_state(admin_chat_id)
        tg.send_message(admin_chat_id, "این تیکت دیگر باز نیست.")
        return

    db.add_ticket_message(ticket_id, "admin", text)
    # حالت باز می‌ماند تا ادمین بتواند بدون فشردن دوباره‌ی «پاسخ»، به گفتگو ادامه دهد
    db.set_admin_state(admin_chat_id, "await_ticket_reply", None, {"ticket_id": ticket_id})

    parts, user_kb = _ticket_user_message(ticket_id, text)
    user_text, user_entities = build_message(parts)
    resp = tg.send_message(ticket["user_id"], user_text, reply_markup=user_kb, entities=user_entities)
    if not (resp and resp.get("ok")):
        tg.send_message(
            admin_chat_id,
            "⚠️ پیام برای کاربر ارسال نشد (احتمالاً ربات را بلاک کرده). پاسخ در تاریخچه‌ی تیکت ثبت شد.",
        )
        return
    _ack_message(admin_chat_id, reply_msg_id, "✅ پیام ارسال شد.")
    _notify_other_admins_ticket(ticket_id, admin_chat_id, from_user, "به این تیکت پاسخ داد", body=text)


# نوع‌های مدیایی که ادمین می‌تواند در جواب تیکت بفرستد
TICKET_MEDIA_LABELS = {
    "photo": "📷 عکس", "video": "🎬 ویدیو", "voice": "🎤 ویس", "audio": "🎵 صوت", "document": "📎 فایل",
}


def _extract_media(message):
    """(نوع، file_id) مدیای پیام، یا (None, None)."""
    if message.get("photo"):
        return "photo", message["photo"][-1]["file_id"]
    for kind in ("video", "voice", "audio", "document"):
        if message.get(kind):
            return kind, message[kind]["file_id"]
    return None, None


def _handle_ticket_admin_media(admin_chat_id, message, state, from_user=None):
    """ادمین در حالت پاسخ به تیکت (یا «پیام به کاربر») عکس/ویدیو/صوت/ویس/فایل فرستاده؛
    همان مدیا (با کپشنش) برای کاربر ارسال و در تاریخچه‌ی تیکت ثبت می‌شود."""
    data = state.get("data") or {}
    if state["action"] == "await_support_msg":
        uid = data.get("uid")
        if not uid or not db.get_user_row(uid):
            db.clear_admin_state(admin_chat_id)
            tg.send_message(admin_chat_id, "کاربر پیدا نشد.")
            return
        open_ticket = db.get_open_ticket_for_user(uid)
        ticket_id = open_ticket["id"] if open_ticket else db.create_ticket(uid)
    else:
        ticket_id = data.get("ticket_id")
    ticket = db.get_ticket(ticket_id) if ticket_id else None
    if not ticket or ticket["status"] != "open":
        db.clear_admin_state(admin_chat_id)
        tg.send_message(admin_chat_id, "این تیکت دیگر باز نیست.")
        return

    kind, file_id = _extract_media(message)
    if not file_id:
        return
    caption = (message.get("caption") or "").strip()
    label = TICKET_MEDIA_LABELS[kind]
    user_id = ticket["user_id"]

    parts, user_kb = _ticket_user_message(ticket_id, caption)
    cap_text, cap_entities = build_message(parts)
    if len(cap_text) <= tg.CAPTION_LIMIT:
        resp = tg.send_media_by_file_id(user_id, kind, file_id, caption=cap_text,
                                        reply_markup=user_kb, caption_entities=cap_entities)
    else:
        # کپشن از سقف تلگرام بلندتر است: اول مدیا، بعد متن کامل با دکمه‌ها
        resp = tg.send_media_by_file_id(user_id, kind, file_id)
        if resp and resp.get("ok"):
            resp = tg.send_message(user_id, cap_text, reply_markup=user_kb, entities=cap_entities)
    if not (resp and resp.get("ok")):
        tg.send_message(
            admin_chat_id,
            "⚠️ ارسال برای کاربر ناموفق بود (احتمالاً ربات را بلاک کرده یا فایل قابل ارسال نیست).",
        )
        return

    logged = f"[{label}] {caption}".strip()
    db.add_ticket_message(ticket_id, "admin", logged)
    db.set_admin_state(admin_chat_id, "await_ticket_reply", None, {"ticket_id": ticket_id})
    _ack_message(admin_chat_id, message.get("message_id"), "✅ پیام ارسال شد.")
    _notify_other_admins_ticket(ticket_id, admin_chat_id, from_user, "به این تیکت پاسخ داد", body=logged)


def _handle_discount_code_entry(chat_id, text, state, from_user):
    """کاربر در حال وارد کردن کد تخفیف است؛ کد را چک می‌کند و بر اساس نتیجه ادامه می‌دهد."""
    if is_sales_paused("gaming"):
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, sales_paused_text("gaming"))
        return
    plan_id = (state.get("data") or {}).get("plan_id")
    plan = db.get_plan(plan_id) if plan_id else None
    if not plan or not plan["active"]:
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, "این پلن دیگر موجود نیست. لطفاً دوباره از /start شروع کنید.")
        return

    code_row = db.get_discount_code_by_text(text)
    if not code_row or not code_row["active"]:
        _prompt_error(
            chat_id,
            "❌ کد تخفیف نامعتبر است یا دیگر فعال نیست.\n"
            "لطفاً دوباره تلاش کنید یا برای ادامه‌ی خرید بدون کد تخفیف، /cancel را بزنید.",
        )
        return

    _drop_prompts(chat_id, state)
    db.clear_admin_state(chat_id)
    # تاییدیه‌ی «کد اعمال شد» جدا فرستاده نمی‌شود؛ خطِ تخفیف داخل پیام پرداخت دیده می‌شود
    _create_order_and_show_payment(chat_id, from_user, plan_id, code_row=code_row)


def _handle_uc_quantity_entry(chat_id, text, state, from_user):
    """تعداد دلخواهی که کاربر تایپ کرده را اعتبارسنجی می‌کند و سفارش یوسی را می‌سازد."""
    if is_sales_paused("uc"):
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, sales_paused_text("uc"))
        return
    uc_id = (state.get("data") or {}).get("uc_id")
    uc = db.get_uc_product(uc_id) if uc_id else None
    if not uc or not uc["active"]:
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, "این آیتم دیگر موجود نیست. لطفاً دوباره از /start شروع کنید.")
        return

    cleaned = normalize_digits(text).replace(",", "").replace("،", "").strip()
    if not cleaned.isdigit() or not 1 <= int(cleaned) <= UC_MAX_QUANTITY:
        _prompt_error(
            chat_id,
            f"❌ لطفاً تعداد را فقط به‌صورت یک عدد از ۱ تا {UC_MAX_QUANTITY} بفرستید. مثلاً: 2\n"
            "(برای انصراف، /cancel را بزنید)",
        )
        return

    _drop_prompts(chat_id, state)
    db.clear_admin_state(chat_id)
    added = _cart_add(from_user["id"], uc_id, int(cleaned))
    if not added:
        tg.send_message(chat_id, f"⚠️ به سقف {UC_MAX_QUANTITY} عدد برای این آیتم رسیده‌اید.")
    _show_uc_cart(from_user["id"])


def _handle_pubg_info_entry(chat_id, text, state, from_user):
    """آیدی/نام اکانت پابجی که کاربر بعد از فرستادن رسید ارسال کرده را ذخیره و به ادمین‌ها اطلاع می‌دهد.
    آیدی عددی اجباری است: اگر پیام کاربر آیدی عددی کامل نداشته باشد، ذخیره نمی‌شود و دوباره می‌خواهیم."""
    order_id = state.get("order_id")
    order = db.get_order(order_id) if order_id else None

    if not order:
        db.clear_admin_state(chat_id)
        tg.send_message(chat_id, "سفارش مربوطه پیدا نشد؛ اگر لازم بود دوباره با ادمین در ارتباط باشید.")
        return

    pubg_id = extract_pubg_id(text)
    if not pubg_id:
        # حالت انتظار را نگه می‌داریم تا کاربر بتواند بلافاصله دوباره بفرستد
        _prompt_error(
            chat_id,
            "❌ <b>آیدی عددی پیدا نشد یا ناقص است!</b>\n\n"
            "آیدی عددی پابجی فقط از <b>رقم</b> تشکیل شده و باید <b>کامل</b> باشد (حداقل ۸ رقم، بدون فاصله).\n\n"
            "⛔️ <b>بدون آیدی عددی درست، سفارش شما انجام نمی‌شود.</b>\n\n"
            "✅ نمونه‌ی درست:\n"
            f"<code>{PUBG_ID_EXAMPLE}</code>\n\n"
            "لطفاً دوباره بفرستید. (برای انصراف /cancel)",
        )
        return

    _drop_prompts(chat_id, state)  # پیام‌های «آیدی را بفرستید» و خطاها بعد از پاسخ درست پاک می‌شوند
    db.clear_admin_state(chat_id)
    clean_text = normalize_digits(text).strip()
    db.set_order_pubg_info(order_id, clean_text)
    tg.send_message(
        chat_id,
        f"✅ آیدی عددی شما ثبت شد: <code>{pubg_id}</code>\n"
        "ممنون از خرید شما 🙏\n\n"
        "اگر آیدی را اشتباه فرستاده‌اید، همین حالا از «ارتباط با ما» به پشتیبانی اطلاع دهید.",
    )

    fresh = db.get_order(order_id) or order
    notice = (
        f"🎮 آیدی عددی سفارش #{order_id} ({html.escape(order['plan_name'] or '')}) رسید:\n"
        f"{uc_total_line(fresh)}\n"
        f"{pubg_id_mono_block(fresh)}"
    )
    # اگر سفارش قبلاً تایید شده، دکمه‌ی «واریز انجام شد» هم کنار همین آیدی می‌آید
    kb = None
    if fresh.get("status") == "approved":
        kb = {"inline_keyboard": [[btn("واریز انجام شد", f"uc_deliver:{order_id}", "check")]]}
        notice += "\n\nسفارش تایید شده است؛ می‌توانید واریز کنید."
    for admin_id in ADMIN_IDS:
        tg.send_message(admin_id, notice, reply_markup=kb)


# ---------------------------------------------------------------------------
# سفارش‌های من: برچسب‌ها و ساخت پیام جزئیات سفارش
# ---------------------------------------------------------------------------

ORDER_STATUS_LABELS = {
    "pending_receipt": "در انتظار پرداخت/رسید",
    "awaiting_kyc": "در انتظار ارسال مدارک احراز هویت",
    "awaiting_review": "در انتظار تایید",
    "approved": "تایید شده - در حال تحویل",
    "delivered": "تحویل داده شده",
    "rejected": "رد شده",
    "expired": "منقضی شده",
    "cancelled": "لغو شده",
}

# «سفارش‌های من»: موفق (تیک سبز)، ناموفق (ضربدر قرمز) و «در انتظار تایید» (ایموجی ساعت: رسید فرستاده
# شده و منتظر بررسی ادمین است). سفارش‌های ناتمام (پرداخت‌نشده / لغو شده) اصلاً نمایش داده نمی‌شوند.
MY_ORDERS_VISIBLE_STATUSES = ("awaiting_kyc", "awaiting_review", "approved", "delivered", "rejected", "expired")

# ایموجی پریمیوم هر وضعیت: موفق = تیک سبز، ناموفق = ضربدر قرمز، در جریان = سفارش در انتظار
ORDER_STATUS_EMOJI = {
    "pending_receipt": "pending",
    "awaiting_kyc": "clock",
    "awaiting_review": "clock",
    "approved": "check",
    "delivered": "check",
    "rejected": "cross",
    "expired": "cross",
    "cancelled": "cross",
}

ORDER_TYPE_LABELS = {
    "plan": "اشتراک گیمینگ وایرگارد",
    "uc": "یوسی پابجی",
    "wallet_topup": "شارژ کیف پول",
}


def _fmt_ts(ts):
    return datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M")


WALLET_TX_LABELS = {
    "opening": "مانده‌ی اولیه",
    "topup": "شارژ کیف پول",
    "purchase": "خرید",
    "referral_bonus": "پورسانت کسب درآمد (اعتبار رفرال)",
    "topup_bonus": "هدیه‌ی شارژ (فقط خرید کانفیگ)",
    "topup_bonus_reversal": "کسر هدیه‌ی شارژِ برگشت‌خورده",
    "adjust": "تعدیل موجودی",
    "refund": "برگشت وجه سفارش لغوشده",
}


def _format_wallet_tx_line(t, include_note=False):
    """یک خط خوانا از یک ردیف دفتر تراکنش: تاریخ | نوع | مبلغ | موجودی بعد از تراکنش."""
    total = (t.get("amount") or 0) + (t.get("referral_amount") or 0)
    sign = "+" if total >= 0 else "−"
    label = WALLET_TX_LABELS.get(t["kind"], t["kind"])
    if t.get("order_id"):
        label += f" (سفارش #{t['order_id']})"
    when = _fmt_ts(t["created_at"]) if t.get("created_at") else "-"
    line = f"{when} | {label} | {sign}{abs(total):,} تومان | موجودی: {(t.get('balance_after') or 0):,}"
    if include_note and t.get("note"):
        line += f" | 📝 {html.escape(str(t['note']))}"
    return line


def build_order_detail_message(o):
    """متن و entity های صفحه‌ی جزئیات یک سفارش را می‌سازد."""
    order_type = o.get("order_type") or "plan"
    parts = [("e", ORDER_STATUS_EMOJI.get(o["status"], "pending")), " ", ("b", f"جزئیات سفارش #{o['id']}"), "\n\n"]
    parts.append(f"نوع سفارش: {ORDER_TYPE_LABELS.get(order_type, order_type)}\n")
    if order_type != "wallet_topup":
        parts.append(f"محصول: {o.get('plan_name') or '-'}\n")
    parts.append(f"مبلغ: {(o.get('price') or 0):,} تومان\n")
    if o.get("discount_code") and (o.get("discount_amount") or 0) > 0:
        parts.append(f"کد تخفیف: {o['discount_code']} (−{o['discount_amount']:,} تومان)\n")
    if order_type == "uc" and o.get("usdt_rate"):
        parts.append(f"نرخ تتر هنگام ثبت: {o['usdt_rate']:,.0f} تومان\n")
    if order_type != "wallet_topup":
        method_label = "کیف پول" if o.get("payment_method") == "wallet" else "کارت به کارت"
        parts.append(f"روش پرداخت: {method_label}\n")
    parts += ["وضعیت: ", ("e", ORDER_STATUS_EMOJI.get(o["status"], "pending")), " ",
              f"{ORDER_STATUS_LABELS.get(o['status'], o['status'])}\n"]
    if o.get("created_at"):
        parts.append(f"تاریخ ثبت: {_fmt_ts(o['created_at'])}\n")
    if order_type == "plan" and o.get("expires_at"):
        parts.append(f"پایان اشتراک: {_fmt_ts(o['expires_at'])}\n")
    if order_type == "uc" and o.get("pubg_info"):
        parts.append(f"اطلاعات اکانت پابجی: {o['pubg_info']}\n")
    if o["status"] == "awaiting_kyc":
        parts.append("\nبرای بررسی این سفارش، عکس کارت بانکی و کارت ملی را همین‌جا بفرستید و دکمه‌ی «تایید و ارسال مدارک» را بزنید.\n")
    if o["status"] == "awaiting_review":
        parts.append("\nرسید شما دریافت شده و پس از بررسی پشتیبانی، نتیجه همین‌جا اطلاع داده می‌شود.\n")
    return build_message(parts)


# ---------------------------------------------------------------------------
# پردازش callback query (دکمه‌های شیشه‌ای)
# ---------------------------------------------------------------------------

def handle_callback_query(cq):
    cq_id = cq["id"]
    data = cq.get("data", "")
    from_chat_id = cq["from"]["id"]
    message = cq.get("message", {})
    message_id = message.get("message_id")

    # محدودیت نرخ درخواست - جلوگیری از اسپم کلیک روی دکمه‌ها
    if not db.check_and_update_rate_limit(from_chat_id, CALLBACK_RATE_LIMIT_SECONDS):
        tg.answer_callback_query(cq_id)
        return

    if data == "noop":
        tg.answer_callback_query(cq_id)
        return

    # اگر کاربر وسط یک «پیام راهنمای موقت» (کد تخفیف / تعداد یوسی / مبلغ شارژ) بود و به‌جای جواب‌دادن
    # دکمه‌ای زد، آن حالت لغو و پیام راهنما پاک می‌شود (به‌جز پیامی که همین الان روی آن کلیک شده)
    _soft_state = db.get_admin_state(from_chat_id)
    if _soft_state and _soft_state.get("action") in SOFT_PROMPT_ACTIONS:
        _drop_prompts(from_chat_id, _soft_state, keep_message_id=message_id)
        db.clear_admin_state(from_chat_id)

    if data == "check_membership":
        user_id = cq["from"].get("id", from_chat_id)
        if is_admin(from_chat_id) or is_member_of_channel(user_id):
            tg.answer_callback_query(cq_id, "✅ عضویت شما تایید شد.")
            send_start_menu(from_chat_id)
        else:
            tg.answer_callback_query(cq_id, "❌ شما هنوز عضو کانال نشده‌اید!", show_alert=True)
            send_force_join_message(from_chat_id)
        return

    if data.startswith("maincat:"):
        which = data.split(":", 1)[1]
        if _block_if_paused(cq_id, which if which in SALES_KINDS else None, from_chat_id):
            return
        if which == "gaming":
            tg.answer_callback_query(cq_id)
            text = "⏳ لطفاً مدت اشتراک مورد نظر را انتخاب کنید:"
            if message_id:
                tg.edit_message_text(from_chat_id, message_id, text, reply_markup=categories_keyboard())
            else:
                tg.send_message(from_chat_id, text, reply_markup=categories_keyboard())
            return
        elif which == "uc":
            usdt = get_usdt_price()
            if usdt is None:
                tg.answer_callback_query(cq_id, "دریافت قیمت لحظه‌ای ناموفق بود، لطفاً دوباره امتحان کنید.", show_alert=True)
                return
            tg.answer_callback_query(cq_id)
            text, entities = build_message([
                ("e", "usdt"), f" نرخ لحظه‌ای تتر: {usdt:,.0f} تومان\n\n",
                ("e", "pricelist"), " لیست قیمت یوسی پابجی:",
            ])
            if message_id:
                tg.edit_message_text(from_chat_id, message_id, text, reply_markup=uc_products_keyboard(usdt, _cart_units(from_chat_id)), entities=entities)
            else:
                tg.send_message(from_chat_id, text, reply_markup=uc_products_keyboard(usdt, _cart_units(from_chat_id)), entities=entities)
            return
        else:
            tg.answer_callback_query(cq_id)
            return

    if data == "back_main":
        tg.answer_callback_query(cq_id)
        text = "🏠 منوی اصلی:\nلطفاً یکی از گزینه‌های زیر را انتخاب کنید:"
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=main_menu_keyboard(is_admin(from_chat_id)))
        else:
            tg.send_message(from_chat_id, text, reply_markup=main_menu_keyboard(is_admin(from_chat_id)))
        return

    if data == "shop_menu":
        tg.answer_callback_query(cq_id)
        text = "🛍 فروشگاه:\nلطفاً دسته مورد نظر را انتخاب کنید:"
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=shop_menu_keyboard())
        else:
            tg.send_message(from_chat_id, text, reply_markup=shop_menu_keyboard())
        return

    if data == "wallet_menu":
        tg.answer_callback_query(cq_id)
        balance = db.get_user_balance(from_chat_id)
        referral_balance = db.get_referral_balance(from_chat_id)
        parts = [
            ("e", "wallet"), " ", ("b", "کیف پول شما"), "\n\n",
            f"موجودی قابل برداشت: ", ("b", f"{balance:,} تومان"), "\n",
        ]
        if referral_balance > 0:
            parts.append(f"اعتبار هدیه/رفرال (فقط خرید پلن/کانفیگ): {referral_balance:,} تومان\n")
        if topup_offer_line():
            parts.append("\n" + topup_offer_line() + "\n")
        parts.append("\nبرای افزایش موجودی، روی «شارژ کیف پول» بزنید.")
        text, entities = build_message(parts)
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=wallet_menu_keyboard(), entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=wallet_menu_keyboard(), entities=entities)
        return

    if data == "wallet_history":
        tg.answer_callback_query(cq_id)
        txs = db.list_wallet_transactions(from_chat_id, limit=10)
        parts = [("e", "history"), " ", ("b", "تاریخچه تراکنش‌های کیف پول"), "\n\n"]
        if not txs:
            parts.append("هنوز تراکنشی ثبت نشده است.")
        else:
            for t in txs:
                parts.append(_format_wallet_tx_line(t) + "\n")
        text, entities = build_message(parts)
        kb = {"inline_keyboard": [nav_row("wallet_menu")]}
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=kb, entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=kb, entities=entities)
        return

    if data == "tutorial_menu":
        tg.answer_callback_query(cq_id)
        text, entities = build_message([("e", "tutorial"), " ", ("b", "آموزش اتصال"),
                                        "\n\nسیستم‌عامل دستگاه خود را انتخاب کنید:"])
        show_screen(from_chat_id, message_id, text, tutorial_menu_keyboard(), entities)
        return

    if data.startswith("tutorial:"):
        platform = data.split(":", 1)[1]
        if platform not in TUTORIAL_PLATFORMS:
            tg.answer_callback_query(cq_id)
            return
        tg.answer_callback_query(cq_id)
        st = db.get_admin_state(from_chat_id)
        if st and st.get("action") == "await_tutorial_text":
            db.clear_admin_state(from_chat_id)  # «پایان» ویرایش
        _send_tutorial(from_chat_id, message_id, platform, admin=is_admin(from_chat_id))
        return

    if data.startswith("tutorial_clearmedia:"):
        platform = data.split(":", 1)[1]
        if not is_admin(from_chat_id) or platform not in TUTORIAL_PLATFORMS:
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.set_setting(f"tutorial_media_{platform}", "[]")
        tg.answer_callback_query(cq_id, "همه‌ی ویدیوها و فایل‌ها حذف شد.")
        _send_tutorial(from_chat_id, message_id, platform, admin=True)
        return

    if data.startswith("tutorial_edit:"):
        platform = data.split(":", 1)[1]
        if not is_admin(from_chat_id) or platform not in TUTORIAL_PLATFORMS:
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_tutorial_text", None, {"platform": platform})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"✏️ آموزش اتصال {TUTORIAL_PLATFORMS[platform]}:\n"
            f"• متن (ساده، حداکثر ۳۵۰۰ کاراکتر) بفرستید تا جایگزین متن فعلی شود؛ فقط - بفرستید تا متن پاک شود.\n"
            f"• ویدیو، عکس یا فایل بفرستید (هر تعداد تا {TUTORIAL_MAX_MEDIA}، با کپشن دلخواه) تا برای کاربرها نمایش داده شود.\n"
            f"وقتی تمام شد «پایان» را بزنید.",
            reply_markup={"inline_keyboard": [[btn("پایان و پیش‌نمایش", f"tutorial:{platform}", "check")]]},
        )
        return

    if data == "wallet_topup_start":
        tg.answer_callback_query(cq_id)
        mid = show_screen(
            from_chat_id, message_id,
            "💳 مبلغی که می‌خواهید کیف پول خود را شارژ کنید را به تومان و فقط به‌صورت عدد ارسال کنید.\n"
            "مثال: 100000" + (("\n\n" + topup_offer_line()) if topup_offer_line() else ""),
            {"inline_keyboard": [nav_row("wallet_menu")]},
        )
        _set_state_with_prompt(from_chat_id, "await_wallet_topup_amount", None, {}, mid)
        return

    if data == "contact_us":
        tg.answer_callback_query(cq_id)
        text, entities = build_message([
            ("e", "contact"), " ", ("b", "ارتباط با پشتیبانی"), "\n\n",
            "برای هرگونه سوال، راهنمایی پیش از خرید یا پیگیری سفارش، خوشحال می‌شویم پاسخگوی شما باشیم.\n\n",
            "برای ارتباط با پشتیبانی، همین‌جا یک تیکت باز کنید (سریع و قابل پیگیری):\n\n",
            "⏰ ساعات پاسخگویی: هر روز، در سریع‌ترین زمان ممکن",
        ])
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=contact_us_keyboard(), entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=contact_us_keyboard(), entities=entities)
        return

    if data == "ticket_start":
        tg.answer_callback_query(cq_id)
        ticket = db.get_open_ticket_for_user(from_chat_id)
        ticket_id = ticket["id"] if ticket else db.create_ticket(from_chat_id)
        db.set_admin_state(from_chat_id, "await_ticket_message", None, {"ticket_id": ticket_id})
        text, entities = build_message([
            ("e", "contact"), " ", ("b", "پشتیبانی"), "\n\n",
            "✍️ همین الان ", ("b", "پیام یا مشکلتان را بنویسید و بفرستید"), " (می‌توانید چند پیام بفرستید).\n\n",
            "⏳ سپس منتظر پاسخ پشتیبانی بمانید؛ جواب همین‌جا برایتان می‌آید.\n\n",
            "✅ بعد از پاسخ، اگر مشکلتان حل شد تیکت را ببندید، و اگر نه گفتگو را ادامه دهید.\n\n",
            f"🕐 تیکتی که بعد از پاسخ پشتیبانی {_ticket_auto_close_label()} بی‌پاسخ بماند، خودکار بسته می‌شود.",
        ])
        show_screen(from_chat_id, message_id, text, {"inline_keyboard": [nav_row("contact_us")]}, entities)
        return

    if data.startswith("ticket_reply_start:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        ticket_id = int(data.split(":", 1)[1])
        ticket = db.get_ticket(ticket_id)
        if not ticket or ticket["status"] != "open":
            tg.answer_callback_query(cq_id, "این تیکت دیگر باز نیست.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_ticket_reply", None, {"ticket_id": ticket_id})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"✍️ پاسخ خود را برای تیکت #{ticket_id} بفرستید؛ متن، عکس، ویدیو، ویس، صوت یا فایل (با کپشن) هر کدام مستقیم برای کاربر می‌رود.\n(برای خروج از حالت پاسخ، /cancel را بزنید)",
        )
        return

    if data.startswith("ticket_close:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        ticket_id = int(data.split(":", 1)[1])
        ticket = db.get_ticket(ticket_id)
        if not ticket:
            tg.answer_callback_query(cq_id, "تیکت پیدا نشد.", show_alert=True)
            return
        was_open = ticket["status"] == "open"
        db.close_ticket(ticket_id)
        if was_open:
            _notify_other_admins_ticket(ticket_id, from_chat_id, cq["from"], "این تیکت را بست", with_buttons=False)
        user_state = db.get_admin_state(ticket["user_id"])
        if user_state and user_state.get("action") == "await_ticket_message" and (user_state.get("data") or {}).get("ticket_id") == ticket_id:
            db.clear_admin_state(ticket["user_id"])
        tg.answer_callback_query(cq_id, "تیکت بسته شد.")
        if message_id:
            tg.edit_message_reply_markup(from_chat_id, message_id)  # دکمه‌ها برداشته می‌شود؛ پیام جدا لازم نیست
        tg.send_message(ticket["user_id"], "🔒 این گفتگوی پشتیبانی بسته شد. در صورت نیاز، دوباره از «ارتباط با ما» تیکت جدید باز کنید.")
        return

    if data.startswith("ticket_close_user:"):
        ticket_id = int(data.split(":", 1)[1])
        ticket = db.get_ticket(ticket_id)
        if not ticket or ticket["user_id"] != from_chat_id:
            tg.answer_callback_query(cq_id, "این تیکت برای شما نیست.", show_alert=True)
            return
        if ticket["status"] != "open":
            tg.answer_callback_query(cq_id, "این تیکت قبلاً بسته شده است.")
            return
        if not db.ticket_has_admin_reply(ticket_id):
            tg.answer_callback_query(cq_id, "هنوز پشتیبانی پاسخی نداده؛ لطفاً منتظر پاسخ بمانید.", show_alert=True)
            return
        db.close_ticket(ticket_id)
        state = db.get_admin_state(from_chat_id)
        if state and state.get("action") == "await_ticket_message":
            db.clear_admin_state(from_chat_id)
        tg.answer_callback_query(cq_id, "گفتگو پایان یافت.")
        tg.send_message(from_chat_id, "🔒 تیکت بسته شد. خوشحالیم که مشکلتان حل شد؛ متشکریم! 🙏")
        return

    if data.startswith("ticket_continue:"):
        ticket_id = int(data.split(":", 1)[1])
        ticket = db.get_ticket(ticket_id)
        if not ticket or ticket["user_id"] != from_chat_id:
            tg.answer_callback_query(cq_id, "این تیکت برای شما نیست.", show_alert=True)
            return
        if ticket["status"] != "open":
            tg.answer_callback_query(cq_id, "این تیکت بسته شده؛ برای پیام جدید از «ارتباط با ما» تیکت جدید بزنید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        resp = tg.send_message(from_chat_id, "✍️ پیام بعدی‌تان را همین‌جا بنویسید و بفرستید؛ مستقیم برای پشتیبانی می‌رود.")
        _set_state_with_prompt(from_chat_id, "await_ticket_message", None, {"ticket_id": ticket_id}, _msg_id(resp))
        return

    if data == "my_orders":
        tg.answer_callback_query(cq_id)
        text, entities = build_message([
            ("e", "pending"), " ", ("b", "سفارش‌های من"), "\n\n",
            "لطفاً دسته‌ی سفارش‌ها را انتخاب کنید:",
        ])
        kb = {"inline_keyboard": [
            [btn("اشتراک گیمینگ", "my_orders:plan", "gaming")],
            [btn("یوسی پابجی", "my_orders:uc", "uc")],
            nav_row("back_main"),
        ]}
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=kb, entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=kb, entities=entities)
        return

    if data.startswith("my_orders:"):
        kind = data.split(":", 1)[1]
        if kind not in ("plan", "uc"):
            tg.answer_callback_query(cq_id)
            return
        tg.answer_callback_query(cq_id)
        title = "اشتراک گیمینگ" if kind == "plan" else "یوسی پابجی"
        title_emoji = "gaming" if kind == "plan" else "uc"
        orders = db.list_orders_for_user_by_type(
            from_chat_id, kind, limit=10, statuses=MY_ORDERS_VISIBLE_STATUSES,
        )
        if not orders:
            text, entities = build_message([
                ("e", title_emoji), " ", ("b", f"سفارش‌های {title}"), "\n\n",
                "هنوز سفارشی در این دسته برای نمایش وجود ندارد.",
            ])
            rows = []
        else:
            text, entities = build_message([
                ("e", title_emoji), " ", ("b", f"سفارش‌های {title}"), "\n\n",
                "برای دیدن جزئیات هر سفارش، روی آن بزنید:",
            ])
            rows = []
            for o in orders:
                label = f"#{o['id']} | {o['plan_name']} | {o['price']:,} تومان"
                rows.append([btn(label, f"order_detail:{o['id']}", ORDER_STATUS_EMOJI.get(o["status"], "pending"))])
        rows.append(nav_row("my_orders"))
        kb = {"inline_keyboard": rows}
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=kb, entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=kb, entities=entities)
        return

    if data.startswith("order_detail:"):
        try:
            order_id = int(data.split(":", 1)[1])
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        order = db.get_order(order_id)
        if (not order or order["user_id"] != from_chat_id
                or order["status"] not in MY_ORDERS_VISIBLE_STATUSES):
            tg.answer_callback_query(cq_id, "این سفارش پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        text, entities = build_order_detail_message(order)
        order_kind = order.get("order_type") or "plan"
        back_cb = f"my_orders:{order_kind}" if order_kind in ("plan", "uc") else "my_orders"
        kb = {"inline_keyboard": [nav_row(back_cb)]}
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=kb, entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=kb, entities=entities)
        return

    if data == "referral_info":
        tg.answer_callback_query(cq_id)
        percent = db.get_referral_percent()
        kb = {"inline_keyboard": [nav_row("back_main")]}
        if percent <= 0:
            text, entities = build_message([
                ("e", "invite"), " سیستم کسب درآمد در حال حاضر توسط مدیریت غیرفعال است."
            ])
        else:
            ref_count = db.count_referrals(from_chat_id)
            ref_balance = db.get_referral_balance(from_chat_id)
            username = get_bot_username()
            link_line = f"🔗 https://t.me/{username}?start=ref_{from_chat_id}" if username else "لینک دعوت شما موقتاً در دسترس نیست."
            text, entities = build_message([
                ("e", "invite"), " ", ("b", "کسب درآمد"), "\n\n",
                f"با معرفی هر دوست، وقتی خرید کانفیگ (اشتراک) انجام دهد، {percent:g}٪ از مبلغ خریدش "
                f"به‌صورت اعتبار به کیف پول شما اضافه می‌شود.\n",
                "این اعتبار فقط برای خرید پلن/کانفیگ قابل استفاده است (نه خرید یوسی).\n\n",
                f"👥 تعداد دعوت‌شدگان: {ref_count}\n",
                f"💰 اعتبار رفرال فعلی: {ref_balance:,} تومان\n\n",
                "لینک اختصاصی دعوت شما:\n",
                link_line,
            ])
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=kb, entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=kb, entities=entities)
        return

    if (data.startswith("cat:") or data == "back_categories") and _block_if_paused(cq_id, "gaming", from_chat_id):
        return

    if data.startswith("cat:"):
        raw_cat_id = data.split(":", 1)[1]
        category_id = None if raw_cat_id == "none" else int(raw_cat_id)
        category = db.get_category(category_id) if category_id is not None else None
        if category_id is not None and (not category or not category["active"]):
            tg.answer_callback_query(cq_id, "این مدت اشتراک دیگر موجود نیست.", show_alert=True)
            return
        title = category["name"] if category else "سایر پلن‌ها"
        tg.answer_callback_query(cq_id)
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, f"🗓 {title}\nپلن‌های موجود:", reply_markup=plans_keyboard(category_id))
        else:
            tg.send_message(from_chat_id, f"🗓 {title}\nپلن‌های موجود:", reply_markup=plans_keyboard(category_id))
        return

    if data == "back_categories":
        tg.answer_callback_query(cq_id)
        text = "⏳ لطفاً مدت اشتراک مورد نظر را انتخاب کنید:"
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=categories_keyboard())
        else:
            tg.send_message(from_chat_id, text, reply_markup=categories_keyboard())
        return

    if data.startswith("buy:"):
        if _block_if_paused(cq_id, "gaming", from_chat_id):
            return
        plan_id = int(data.split(":", 1)[1])
        plan = db.get_plan(plan_id)
        if not plan or not plan["active"]:
            tg.answer_callback_query(cq_id, "این پلن دیگر موجود نیست.", show_alert=True)
            return

        tg.answer_callback_query(cq_id)
        show_screen(
            from_chat_id, message_id,
            f"پلن انتخابی: {html.escape(plan['name'])} - {plan['price']:,} تومان\n\n"
            f"آیا کد تخفیف دارید؟",
            discount_choice_keyboard(plan_id),
        )
        return

    if data.startswith("renew:"):
        if _block_if_paused(cq_id, "gaming", from_chat_id):
            return
        plan_id = int(data.split(":", 1)[1])
        plan = db.get_plan(plan_id)
        if not plan or not plan["active"]:
            tg.answer_callback_query(cq_id, "این پلن دیگر موجود نیست؛ لطفاً از فروشگاه پلن جدیدی انتخاب کنید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _create_order_and_show_payment(from_chat_id, cq["from"], plan_id, message_id=message_id)
        return

    if data.startswith("buyuc:"):
        if _block_if_paused(cq_id, "uc", from_chat_id):
            return
        # مرحله‌ی اول: بعد از انتخاب آیتم، تعداد را می‌پرسیم (سفارش هنوز ساخته نمی‌شود)
        uc_id = int(data.split(":", 1)[1])
        uc = db.get_uc_product(uc_id)
        if not uc or not uc["active"]:
            tg.answer_callback_query(cq_id, "این آیتم دیگر موجود نیست.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        text = uc_quantity_prompt_text(uc)
        kb = uc_quantity_keyboard(uc_id)
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=kb)
        else:
            tg.send_message(from_chat_id, text, reply_markup=kb)
        return

    if data.startswith("buyucq:"):
        if _block_if_paused(cq_id, "uc", from_chat_id):
            return
        # مرحله‌ی دوم: تعداد انتخاب شد -> استعلام قیمت لحظه‌ای و ساخت سفارش
        try:
            _, uc_id_str, qty_str = data.split(":", 2)
            uc_id, quantity = int(uc_id_str), int(qty_str)
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        uc = db.get_uc_product(uc_id)
        if not uc or not uc["active"]:
            tg.answer_callback_query(cq_id, "این آیتم دیگر موجود نیست.", show_alert=True)
            return
        if not 1 <= quantity <= UC_MAX_QUANTITY:
            tg.answer_callback_query(cq_id, "تعداد نامعتبر است.", show_alert=True)
            return
        added = _cart_add(from_chat_id, uc_id, quantity)
        tg.answer_callback_query(cq_id, "✅ به سبد خرید اضافه شد." if added else "⚠️ به سقف تعداد این آیتم رسیده‌اید.")
        _show_uc_cart(from_chat_id, message_id=message_id)
        return

    if data.startswith("buyucx:"):
        if _block_if_paused(cq_id, "uc", from_chat_id):
            return
        # «تعداد دیگر»: کاربر خودش عدد را تایپ می‌کند
        uc_id = int(data.split(":", 1)[1])
        uc = db.get_uc_product(uc_id)
        if not uc or not uc["active"]:
            tg.answer_callback_query(cq_id, "این آیتم دیگر موجود نیست.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        mid = show_screen(
            from_chat_id, message_id,
            f"🔢 تعداد مورد نظر برای «{html.escape(uc['name'])}» را فقط به‌صورت عدد بفرستید "
            f"(از ۱ تا {UC_MAX_QUANTITY}).\n\n"
            "مثال: برای خرید ۱۲۰ یوسی با آیتم ۶۰ یوسی، عدد 2 را بفرستید.",
            {"inline_keyboard": [nav_row("maincat:uc")]},
        )
        _set_state_with_prompt(from_chat_id, "await_uc_quantity", None, {"uc_id": uc_id}, mid)
        return

    if data.startswith("uc_requote:"):
        if _block_if_paused(cq_id, "uc", from_chat_id):
            return
        # سفارش یوسی منقضی‌شده را دوباره داخل سبد می‌گذارد تا با قیمت لحظه‌ای جدید ثبت شود
        order_id = int(data.split(":", 1)[1])
        order = db.get_order(order_id)
        if not order or order["user_id"] != from_chat_id or order.get("order_type") != "uc":
            tg.answer_callback_query(cq_id, "این سفارش برای شما نیست.", show_alert=True)
            return
        if order["status"] not in ("expired", "pending_receipt"):
            tg.answer_callback_query(cq_id, "این سفارش دیگر قابل ثبت مجدد نیست.", show_alert=True)
            return
        # اول وضعیت را اتمیک عوض می‌کنیم تا دوبار زدن دکمه، تعداد را دو برابر نکند
        if not db.claim_order(order_id, order["status"], "cancelled"):
            tg.answer_callback_query(cq_id, "این سفارش قبلاً دوباره ثبت شده است.", show_alert=True)
            return
        by_name = {u["name"]: u for u in db.list_active_uc_products()}
        restored = 0
        for part in (order.get("plan_name") or "").split(" + "):
            m = re.match(r"^(.*?)(?:\s*×\s*(\d+))?$", part.strip())
            uc = by_name.get(m.group(1).strip()) if m else None
            if uc:
                # تعداد را «تنظیم» می‌کنیم نه «اضافه»، تا با آیتم‌های موجود سبد جمع نشود
                db.cart_set_quantity(from_chat_id, uc["id"], min(int(m.group(2) or 1), UC_MAX_QUANTITY))
                restored += 1
        if not restored:
            tg.answer_callback_query(cq_id, "آیتم‌های این سفارش دیگر موجود نیستند.", show_alert=True)
            return
        tg.answer_callback_query(cq_id, "در حال استعلام قیمت جدید...")
        _show_uc_cart(from_chat_id, message_id=message_id)
        return

    if data == "uccart":
        if _block_if_paused(cq_id, "uc", from_chat_id):
            return
        tg.answer_callback_query(cq_id)
        _show_uc_cart(from_chat_id, message_id=message_id)
        return

    if data.startswith("cartq:"):
        if _block_if_paused(cq_id, "uc", from_chat_id):
            return
        try:
            _, uc_id_str, delta_str = data.split(":", 2)
            uc_id, delta = int(uc_id_str), int(delta_str)
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        current = db.cart_get_quantity(from_chat_id, uc_id)
        if current:
            new_qty = max(0, min(current + delta, UC_MAX_QUANTITY))
            db.cart_set_quantity(from_chat_id, uc_id, new_qty)
            if new_qty == current:
                tg.answer_callback_query(cq_id, f"حداکثر تعداد برای هر آیتم {UC_MAX_QUANTITY} است.")
            else:
                tg.answer_callback_query(cq_id)
        else:
            tg.answer_callback_query(cq_id)
        _show_uc_cart(from_chat_id, message_id=message_id)
        return

    if data.startswith("cartdel:"):
        uc_id = int(data.split(":", 1)[1])
        db.cart_set_quantity(from_chat_id, uc_id, 0)
        tg.answer_callback_query(cq_id, "از سبد حذف شد.")
        _show_uc_cart(from_chat_id, message_id=message_id)
        return

    if data == "cartclear":
        db.cart_clear(from_chat_id)
        tg.answer_callback_query(cq_id, "سبد خرید خالی شد.")
        _show_uc_cart(from_chat_id, message_id=message_id)
        return

    if data == "cartcheckout":
        if _block_if_paused(cq_id, "uc", from_chat_id):
            return
        tg.answer_callback_query(cq_id, "در حال استعلام قیمت لحظه‌ای...")
        _checkout_uc_cart(from_chat_id, cq["from"], message_id=message_id)
        return

    if data.startswith("discount_no:"):
        if _block_if_paused(cq_id, "gaming", from_chat_id):
            return
        plan_id = int(data.split(":", 1)[1])
        plan = db.get_plan(plan_id)
        if not plan or not plan["active"]:
            tg.answer_callback_query(cq_id, "این پلن دیگر موجود نیست.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _create_order_and_show_payment(from_chat_id, cq["from"], plan_id, message_id=message_id)
        return

    if data.startswith("discount_yes:"):
        if _block_if_paused(cq_id, "gaming", from_chat_id):
            return
        plan_id = int(data.split(":", 1)[1])
        plan = db.get_plan(plan_id)
        if not plan or not plan["active"]:
            tg.answer_callback_query(cq_id, "این پلن دیگر موجود نیست.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        mid = show_screen(
            from_chat_id, message_id,
            "لطفاً کد تخفیف خود را ارسال کنید.",
            {"inline_keyboard": [[btn("ادامه بدون کد تخفیف", f"discount_no:{plan_id}", "cross")]]},
        )
        _set_state_with_prompt(from_chat_id, "await_discount_code", None, {"plan_id": plan_id}, mid)
        return

    if data.startswith("cancel_order:"):
        try:
            order_id = int(data.split(":", 1)[1])
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        order = db.get_order(order_id)
        if not order or order["user_id"] != from_chat_id:
            tg.answer_callback_query(cq_id, "این سفارش برای شما نیست.", show_alert=True)
            return
        # لغو اتمیک: فقط سفارشی که هنوز رسید/پرداختی برایش ثبت نشده قابل لغو است
        if not db.cancel_pending_order(order_id, from_chat_id):
            tg.answer_callback_query(
                cq_id,
                "این سفارش دیگر قابل لغو نیست (قبلاً لغو، پرداخت یا در حال بررسی است).",
                show_alert=True,
            )
            return
        tg.answer_callback_query(cq_id, "سفارش لغو شد.")
        text, entities = build_message([
            ("e", "cross"), f" سفارش #{order_id} لغو شد.\n\n",
            "هر زمان خواستید می‌توانید از منوی اصلی سفارش جدید ثبت کنید.",
        ])
        kb = {"inline_keyboard": [[btn("منوی اصلی", "back_main", "home")]]}
        if message_id:
            tg.edit_message_text(from_chat_id, message_id, text, reply_markup=kb, entities=entities)
        else:
            tg.send_message(from_chat_id, text, reply_markup=kb, entities=entities)
        return

    if data.startswith("pay_wallet:"):
        order_id = int(data.split(":", 1)[1])
        order = db.get_order(order_id)
        if not order or order["user_id"] != from_chat_id:
            tg.answer_callback_query(cq_id, "این سفارش برای شما نیست.", show_alert=True)
            return
        if _block_if_paused(cq_id, _order_sales_kind(order), from_chat_id):
            return
        if order["status"] != "pending_receipt":
            tg.answer_callback_query(cq_id, "این سفارش لغو شده، منقضی شده یا قبلاً پرداخت شده است. برای خرید، سفارش جدید ثبت کنید.", show_alert=True)
            return
        if _uc_order_is_stale(order):
            tg.answer_callback_query(cq_id)
            _expire_uc_order_and_notify(order, from_chat_id)
            return
        tg.answer_callback_query(cq_id)
        _pay_with_wallet(from_chat_id, cq["from"], order, message_id=message_id)
        return

    if data.startswith("pay_card:"):
        order_id = int(data.split(":", 1)[1])
        order = db.get_order(order_id)
        if not order or order["user_id"] != from_chat_id:
            tg.answer_callback_query(cq_id, "این سفارش برای شما نیست.", show_alert=True)
            return
        if _block_if_paused(cq_id, _order_sales_kind(order), from_chat_id):
            return
        if order["status"] != "pending_receipt":
            tg.answer_callback_query(cq_id, "این سفارش لغو شده، منقضی شده یا قبلاً پرداخت شده است. برای خرید، سفارش جدید ثبت کنید.", show_alert=True)
            return
        if _uc_order_is_stale(order):
            tg.answer_callback_query(cq_id)
            _expire_uc_order_and_notify(order, from_chat_id)
            return
        tg.answer_callback_query(cq_id)
        db.set_order_payment_method(order_id, "card")
        _show_card_payment_info(from_chat_id, order, message_id=message_id)
        return

    if data.startswith("msguser:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        try:
            target_uid = int(data.split(":", 1)[1])
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        if not db.get_user_row(target_uid):
            tg.answer_callback_query(cq_id, "کاربر پیدا نشد.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_support_msg", None, {"uid": target_uid})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"✍️ پیامی را که می‌خواهید از طرف پشتیبانی برای کاربر <code>{target_uid}</code> بفرستید، همین‌جا بنویسید "
            f"(مثلاً: «مبلغ شارژ را اشتباه واریز کرده‌اید»).\n"
            f"پیام با عنوان «پیام از پشتیبانی» ارسال می‌شود و کاربر با دکمه‌ی «پاسخ به پشتیبانی» می‌تواند جواب بدهد؛ جوابش برایتان می‌آید.\n\n"
            f"(برای انصراف، /cancel)",
        )
        return

    if data == "cancelorder_no":
        tg.answer_callback_query(cq_id, "انصراف شد.")
        return

    if data.startswith("cancelorder:") or data.startswith("cancelrf:"):
        _handle_admin_cancel_order(cq, data)
        return

    if data.startswith("resume:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        order_id = int(data.split(":", 1)[1])
        order = db.get_order(order_id)
        if not order or order["status"] != "approved":
            tg.answer_callback_query(cq_id, "این سفارش دیگر در این وضعیت نیست.", show_alert=True)
            return
        if order.get("order_type") == "uc":
            tg.answer_callback_query(cq_id, "این سفارش یوسی است؛ بعد از واریز دستی، «واریز انجام شد» را بزنید.", show_alert=True)
            tg.send_message(
                from_chat_id,
                f"📦 سفارش یوسی #{order_id} منتظر واریز است.\n{pubg_id_mono_block(order)}\n\nپس از واریز دستی UC، دکمه‌ی زیر را بزنید.",
                reply_markup={"inline_keyboard": [[btn("واریز انجام شد", f"uc_deliver:{order_id}", "check")]]},
            )
            return
        tg.answer_callback_query(cq_id, "آماده دریافت فایل.")
        resp = tg.send_message(from_chat_id, f"حالا فایل(های) کانفیگ سفارش #{order_id} را بفرستید (می‌توانید چند فایل پشت‌سرهم بفرستید).")
        db.set_admin_state(from_chat_id, "await_config_file", order_id, {"sent_count": 0, "msg_id": _msg_id(resp)})
        _notify_other_admins(order_id, from_chat_id, "📎 در حال ارسال فایل", cq["from"])
        return

    if data.startswith("notifyme:"):
        kind = data.split(":", 1)[1]
        if kind in SALES_KINDS:
            db.add_sales_notify(from_chat_id, kind)
            tg.answer_callback_query(cq_id, f"🔔 ثبت شد؛ وقتی فروش {SALES_KINDS[kind]} شروع شد خبرتان می‌کنیم.", show_alert=True)
        else:
            tg.answer_callback_query(cq_id)
        return

    if data.startswith("admin_pause_go:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        kind = data.split(":", 1)[1]
        if kind in SALES_KINDS:
            tg.answer_callback_query(cq_id, f"⏸ فروش {SALES_KINDS[kind]} متوقف شد.")
            _apply_pause(from_chat_id, kind, "")
        else:
            tg.answer_callback_query(cq_id)
        return

    if data == "admin_bonus_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_topup_bonus_min", None, {})
        tg.answer_callback_query(cq_id)
        mn, bonus = get_topup_bonus()
        cur = f"شارژ {mn:,} تومان یا بیشتر ← {bonus:,} تومان هدیه" if mn else "غیرفعال"
        tg.send_message(
            from_chat_id,
            f"🎁 آفر هدیه‌ی شارژ کیف پول\n\nوضعیت فعلی: {cur}\n\n"
            f"هدیه به «اعتبار محدود» کاربر اضافه می‌شود و فقط برای خرید پلن/کانفیگ قابل استفاده است (نه یوسی).\n\n"
            f"مرحله ۱: حداقل مبلغ شارژ برای گرفتن هدیه را فقط با عدد (تومان) بفرستید، مثلاً: 500000\n"
            f"(با ارسال 0 آفر غیرفعال می‌شود)\n\n(برای انصراف، /cancel)",
        )
        return

    if data == "admin_kyc_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_kyc_threshold", None, {})
        tg.answer_callback_query(cq_id)
        thr = get_kyc_threshold()
        cur = f"{thr:,} تومان" if thr > 0 else "غیرفعال"
        tg.send_message(
            from_chat_id,
            f"🪪 حد مبلغ احراز هویت\n\n"
            f"مقدار فعلی: {cur}\n"
            f"برای پرداخت‌های (کارت‌به‌کارت یا شارژ کیف پول) بیشتر از این مبلغ، بعد از ارسال رسید از کاربر "
            f"عکس کارت بانکی و کارت ملی خواسته می‌شود.\n\n"
            f"مبلغ جدید را فقط با عدد و به تومان بفرستید، مثلاً: 2000000\n"
            f"(با ارسال 0 احراز هویت غیرفعال می‌شود)\n\n(برای انصراف، /cancel)",
        )
        return

    if data.startswith("kyc_confirm:"):
        _handle_kyc_confirm(cq, data)
        return

    if data.startswith("admin_togglesale:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        kind = data.split(":", 1)[1]
        if kind not in SALES_KINDS:
            tg.answer_callback_query(cq_id)
            return
        if not is_sales_paused(kind):
            # قبل از توقف، دلیل را از ادمین می‌پرسیم تا به کاربران نشان داده شود
            db.set_admin_state(from_chat_id, "await_pause_reason", None, {"kind": kind})
            tg.answer_callback_query(cq_id)
            tg.send_message(
                from_chat_id,
                f"⏸ توقف فروش {SALES_KINDS[kind]}\n\n"
                f"توضیحات یا دلیل توقف را بنویسید و بفرستید؛ همین متن به کاربرانی که می‌خواهند "
                f"خرید کنند نشان داده می‌شود (حداکثر ۳۰۰ کاراکتر).\n"
                f"اگر توضیحی لازم ندارید، دکمه‌ی زیر را بزنید.\n\n(برای انصراف، /cancel)",
                reply_markup={"inline_keyboard": [[btn("بدون توضیح، فقط متوقف کن", f"admin_pause_go:{kind}", "disable")]]},
            )
            return
        now_paused = False
        db.set_setting(f"sales_paused_{kind}", "0")
        db.set_setting(f"sales_paused_reason_{kind}", "")
        _notify_sales_resumed(kind)
        label = SALES_KINDS[kind]
        tg.answer_callback_query(
            cq_id,
            f"⏸ فروش {label} متوقف شد." if now_paused else f"✅ فروش {label} دوباره فعال شد.",
        )
        text, entities = _admin_sales_text()
        _show_admin_screen(from_chat_id, message_id, text, entities, admin_sales_keyboard())
        return

    if data.startswith(("admin_admins", "admin_add_start", "admin_rm:", "admin_rm_yes:")):
        if not is_main_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "فقط ادمین اصلی می‌تواند ادمین‌ها را مدیریت کند.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        if data == "admin_add_start":
            _set_state_with_prompt(from_chat_id, "await_admin_add", None, {}, None)
            show_screen(from_chat_id, message_id,
                        "➕ آیدی عددی ادمین جدید (یا یوزرنیمِ کسی که ربات را استارت کرده) را بفرستید.\n"
                        "ادمین جدید باید حداقل یک‌بار ربات را استارت کرده باشد.\n\n(/cancel برای انصراف)",
                        {"inline_keyboard": [[btn("بازگشت", "admin_admins", "back")]]})
            return
        if data.startswith("admin_rm:"):
            rid = int(data.split(":", 1)[1])
            show_screen(from_chat_id, message_id, f"⚠️ دسترسی ادمین <code>{rid}</code> برداشته شود؟",
                        {"inline_keyboard": [[btn("بله، حذف کن", f"admin_rm_yes:{rid}", "check"),
                                              btn("انصراف", "admin_admins", "cross")]]})
            return
        if data.startswith("admin_rm_yes:"):
            rid = int(data.split(":", 1)[1])
            save_extra_admin_ids([a for a in get_extra_admin_ids() if a != rid])
            db.clear_admin_state(rid)
            tg.send_message(rid, "دسترسی ادمین شما از ربات برداشته شد.")
        _show_admins_screen(from_chat_id, message_id)
        return

    if data in ("admin_menu", "admin_sales", "admin_offers") or data.startswith("admin_sec:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        if data == "admin_menu":
            text, entities = admin_home_text()
            kb = admin_menu_keyboard(from_chat_id)
        elif data == "admin_sales":
            text, entities = _admin_sales_text()
            kb = admin_sales_keyboard()
        elif data == "admin_offers":
            text, entities = _admin_offers_text()
            kb = admin_offers_keyboard()
        else:
            key = data.split(":", 1)[1]
            if key not in ADMIN_SECTIONS:
                return
            text, entities = admin_section_text(key)
            kb = admin_section_keyboard(key)
        _show_admin_screen(from_chat_id, message_id, text, entities, kb)
        return

    if data == "admin_addplan_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        categories = db.list_all_categories()
        if not categories:
            tg.answer_callback_query(cq_id)
            tg.send_message(
                from_chat_id,
                "ابتدا باید حداقل یک مدت اشتراک بسازید (مثلاً «یک ماهه»، «دو ماهه»).\n"
                f"برای ساخت آن، از «مدیریت فروش ← مدت‌های اشتراک ← {emoji_html('plus')} افزودن» در /admin استفاده کنید.",
            )
            return
        buttons = [[btn(c['name'], f"admin_addplan_pickcat:{c['id']}", "duration")] for c in categories]
        tg.answer_callback_query(cq_id)
        tg.send_message(from_chat_id, "این پلن جدید را برای کدام مدت اشتراک می‌سازید؟", reply_markup={"inline_keyboard": buttons})
        return

    if data.startswith("admin_addplan_pickcat:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        category_id = int(data.split(":", 1)[1])
        category = db.get_category(category_id)
        if not category:
            tg.answer_callback_query(cq_id, "این مدت اشتراک دیگر موجود نیست.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_plan_name", None, {"category_id": category_id})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"مدت اشتراک: «{category['name']}»\n\n"
            f"نام پلن جدید را بفرستید (مثلاً: ۱۰ گیگ)\n\nهر وقت خواستید، /cancel را بزنید تا لغو شود.",
        )
        return

    if data == "admin_listcats":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _send_admin_categories_list(from_chat_id)
        return

    if data == "admin_addcat_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_category_name", None, {})
        tg.answer_callback_query(cq_id)
        tg.send_message(from_chat_id, "نام مدت اشتراک جدید را بفرستید (مثلاً: یک ماهه)\n\nهر وقت خواستید، /cancel را بزنید تا لغو شود.")
        return

    if data.startswith("toggle_cat:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        _, cat_id_str, new_state = data.split(":", 2)
        cat_id = int(cat_id_str)
        if new_state == "off":
            db.deactivate_category(cat_id)
        else:
            db.activate_category(cat_id)
        category = db.get_category(cat_id)
        tg.answer_callback_query(cq_id, "به‌روزرسانی شد.")
        if category and message_id:
            state_label = "🟢 فعال" if category["active"] else "🔴 غیرفعال"
            plan_count = db.count_all_plans_in_category(cat_id)
            caption = f"#{category['id']} - {category['name']}\n{state_label} | تعداد پلن‌ها: {plan_count}"
            tg.edit_message_text(from_chat_id, message_id, caption, reply_markup=category_row_keyboard(category))
        return

    if data.startswith("editcat_start:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        cat_id = int(data.split(":", 1)[1])
        cat = db.get_category(cat_id)
        if not cat:
            tg.answer_callback_query(cq_id, "مدت اشتراک پیدا نشد.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_edit_category", None, {"category_id": cat_id})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"در حال ویرایش «{cat['name']}».\n\n"
            f"مقدار جدید را به این فرمت بفرستید:\nنام | مدت به روز\n\n"
            f"برای رد کردن (نگه‌داشتن) یک فیلد، به‌جایش - بگذارید.\n"
            f"مقدار فعلی: {cat['name']} | {cat.get('duration_days') or 0}\n\n"
            f"(برای انصراف، /cancel را بزنید)",
        )
        return
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        cat_id = int(data.split(":", 1)[1])
        category = db.get_category(cat_id)
        if not category:
            tg.answer_callback_query(cq_id, "مدت اشتراک پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"⚠️ آیا مطمئنید می‌خواهید مدت اشتراک «{category['name']}» را کامل حذف کنید؟\n"
            f"پلن‌های داخل آن حذف نمی‌شوند، فقط بدون مدت اشتراک می‌شوند.",
            reply_markup={
                "inline_keyboard": [[
                    {"text": "❗️بله، کامل حذف کن", "callback_data": f"delcat_confirm:{cat_id}"},
                    {"text": "انصراف", "callback_data": "delcat_cancel"},
                ]]
            },
        )
        return

    if data.startswith("delcat_confirm:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        cat_id = int(data.split(":", 1)[1])
        category = db.get_category(cat_id)
        db.delete_category(cat_id)
        tg.answer_callback_query(cq_id, "حذف شد.")
        name = category["name"] if category else f"#{cat_id}"
        show_screen(from_chat_id, message_id, f"🗑 مدت اشتراک «{name}» برای همیشه حذف شد.")
        return

    if data == "delcat_cancel":
        tg.answer_callback_query(cq_id, "انصراف داده شد.")
        return

    if data == "admin_addcode_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        tg.send_message(from_chat_id, "نوع تخفیف را انتخاب کنید:", reply_markup=discount_type_keyboard())
        return

    if data.startswith("addcode_type:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        dtype = data.split(":", 1)[1]  # 'percent' یا 'amount'
        db.set_admin_state(from_chat_id, "await_code_text", None, {"type": dtype})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            "کد تخفیف را بفرستید (می‌تواند ترکیبی از عدد و حروف باشد، بدون فاصله)، مثلاً: OFF20\n\n"
            "هر وقت خواستید، /cancel را بزنید تا لغو شود.",
        )
        return

    if data == "admin_listcodes":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _send_admin_discount_codes_list(from_chat_id)
        return

    if data.startswith("toggle_code:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        _, code_id_str, new_state = data.split(":", 2)
        code_id = int(code_id_str)
        if new_state == "off":
            db.deactivate_discount_code(code_id)
        else:
            db.activate_discount_code(code_id)
        code_row = db.get_discount_code(code_id)
        tg.answer_callback_query(cq_id, "به‌روزرسانی شد.")
        if code_row and message_id:
            state_label = "🟢 فعال" if code_row["active"] else "🔴 غیرفعال"
            value_label = f"{code_row['value']}٪" if code_row["type"] == "percent" else f"{code_row['value']:,} تومان"
            caption = f"🏷 کد: {code_row['code']}\nنوع تخفیف: {value_label}\nوضعیت: {state_label}"
            tg.edit_message_text(from_chat_id, message_id, caption, reply_markup=discount_code_row_keyboard(code_row))
        return

    if data.startswith("editcode_start:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        code_id = int(data.split(":", 1)[1])
        code_row = db.get_discount_code(code_id)
        if not code_row:
            tg.answer_callback_query(cq_id, "کد پیدا نشد.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_edit_code_value", None, {"code_id": code_id})
        tg.answer_callback_query(cq_id)
        unit = "درصد (عدد بین ۱ تا ۱۰۰)" if code_row["type"] == "percent" else "مبلغ به تومان"
        tg.send_message(
            from_chat_id,
            f"در حال ویرایش کد «{code_row['code']}».\n\nمقدار فعلی: {code_row['value']}\n"
            f"مقدار جدید را به‌صورت {unit} بفرستید.\n\n(برای انصراف، /cancel را بزنید)",
        )
        return

    if data.startswith("delcode_ask:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        code_id = int(data.split(":", 1)[1])
        code_row = db.get_discount_code(code_id)
        if not code_row:
            tg.answer_callback_query(cq_id, "کد تخفیف پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"⚠️ آیا مطمئنید می‌خواهید کد تخفیف «{code_row['code']}» را کامل و برای همیشه حذف کنید؟",
            reply_markup={
                "inline_keyboard": [[
                    {"text": "❗️بله، کامل حذف کن", "callback_data": f"delcode_confirm:{code_id}"},
                    {"text": "انصراف", "callback_data": "delcode_cancel"},
                ]]
            },
        )
        return

    if data.startswith("delcode_confirm:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        code_id = int(data.split(":", 1)[1])
        code_row = db.get_discount_code(code_id)
        db.delete_discount_code(code_id)
        tg.answer_callback_query(cq_id, "حذف شد.")
        name = code_row["code"] if code_row else f"#{code_id}"
        show_screen(from_chat_id, message_id, f"🗑 کد تخفیف «{name}» برای همیشه حذف شد.")
        return

    if data == "delcode_cancel":
        tg.answer_callback_query(cq_id, "انصراف داده شد.")
        return

    if data == "admin_listplans":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _send_admin_plans_list(from_chat_id)
        return

    if data == "admin_listuc":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _send_admin_uc_list(from_chat_id)
        return

    if data == "admin_adduc_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_uc_name", None, {})
        tg.answer_callback_query(cq_id)
        tg.send_message(from_chat_id, "نام آیتم یوسی جدید را بفرستید (مثلاً: 60 UC)\n\nبرای انصراف /cancel را بزنید.")
        return

    if data.startswith("deluc_ask:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        uc_id = int(data.split(":", 1)[1])
        uc = db.get_uc_product(uc_id)
        if not uc:
            tg.answer_callback_query(cq_id, "آیتم پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"⚠️ آیا مطمئنید می‌خواهید آیتم «{uc['name']}» را کامل و برای همیشه حذف کنید؟\n"
            f"این کار قابل بازگشت نیست (سفارش‌های قبلی این آیتم دست‌نخورده می‌مانند).",
            reply_markup={
                "inline_keyboard": [[
                    {"text": "❗️بله، کامل حذف کن", "callback_data": f"deluc_confirm:{uc_id}"},
                    {"text": "انصراف", "callback_data": "deluc_cancel"},
                ]]
            },
        )
        return

    if data.startswith("deluc_confirm:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        uc_id = int(data.split(":", 1)[1])
        uc = db.get_uc_product(uc_id)
        db.delete_uc_product(uc_id)
        tg.answer_callback_query(cq_id, "حذف شد.")
        name = uc["name"] if uc else f"#{uc_id}"
        show_screen(from_chat_id, message_id, f"🗑 آیتم «{name}» برای همیشه حذف شد.")
        return

    if data == "deluc_cancel":
        tg.answer_callback_query(cq_id, "انصراف داده شد.")
        return

    if data.startswith("toggle_uc:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        _, uc_id_str, new_state = data.split(":", 2)
        uc_id = int(uc_id_str)
        if new_state == "off":
            db.deactivate_uc_product(uc_id)
        else:
            db.activate_uc_product(uc_id)
        uc = db.get_uc_product(uc_id)
        tg.answer_callback_query(cq_id, "به‌روزرسانی شد.")
        if uc and message_id:
            state_label = "🟢 فعال" if uc["active"] else "🔴 غیرفعال"
            caption = f"#{uc['id']} - {uc['name']}\nضریب: {uc['factor']}\nوضعیت: {state_label}"
            tg.edit_message_text(from_chat_id, message_id, caption, reply_markup=uc_row_keyboard(uc))
        return

    if data.startswith("edituc_start:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        uc_id = int(data.split(":", 1)[1])
        uc = db.get_uc_product(uc_id)
        if not uc:
            tg.answer_callback_query(cq_id, "آیتم یوسی پیدا نشد.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_edit_uc_factor", None, {"uc_id": uc_id})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"در حال ویرایش «{uc['name']}».\n\nضریب فعلی: {uc['factor']}\n"
            f"ضریب جدید را بفرستید (مثلاً: 0.955)\n\n(برای انصراف، /cancel را بزنید)",
        )
        return

    if data.startswith("pending_page:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        if message_id:
            tg.delete_message(from_chat_id, message_id)
        _send_pending_orders(from_chat_id, page=int(data.split(":", 1)[1]))
        return

    if data == "admin_pending":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _send_pending_orders(from_chat_id)
        return

    if data == "admin_broadcast_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _start_broadcast_wizard(from_chat_id)
        return

    if data == "admin_setreferral_start":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_referral_percent", None, {})
        tg.answer_callback_query(cq_id)
        current = db.get_referral_percent()
        tg.send_message(
            from_chat_id,
            f"درصد فعلی پورسانت رفرال: {current:g}٪\n\n"
            f"درصد جدید را بفرستید (عدد بین ۰ تا ۱۰۰؛ برای مثال 1 یعنی ۱٪). با ارسال 0 سیستم رفرال غیرفعال می‌شود.\n"
            f"این اعتبار فقط در خرید پلن/کانفیگ قابل استفاده خواهد بود، نه خرید یوسی.\n\n"
            f"(برای انصراف، /cancel را بزنید)",
        )
        return

    if data == "admin_report":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _send_sales_report(from_chat_id, message_id=message_id)
        return

    if data == "admin_userscount":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        total = db.count_users()
        blocked = db.count_blocked_users()
        parts = [
            ("e", "admin"), " ", ("b", "تعداد کاربران ربات"), "\n\n",
            ("e", "check"), f" تعداد کل کاربرانی که تاکنون /start زده‌اند: ", ("b", f"{total:,}"), " نفر\n",
            f"🚫 ربات را بلاک کرده‌اند: {blocked:,} نفر (از پیام‌های همگانی کنار گذاشته می‌شوند)\n",
            f"📨 قابل دریافت پیام همگانی: {total - blocked:,} نفر\n",
        ]
        text, entities = build_message(parts)
        show_screen(from_chat_id, message_id, text, admin_menu_keyboard(from_chat_id), entities)
        return

    if data == "admin_tickets":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        tickets = db.list_open_tickets()
        if not tickets:
            show_screen(from_chat_id, message_id, "🎫 هیچ تیکت بازی وجود ندارد.", admin_menu_keyboard(from_chat_id))
        else:
            for t in tickets:
                messages = db.list_ticket_messages(t["id"], limit=5)
                last_msgs = "\n".join(f"[{m['sender']}] {m['text'][:60]}" for m in messages[-3:])
                caption = f"🎫 تیکت #{t['id']} | کاربر: {t['user_id']}\n\n{last_msgs}"
                kb = {
                    "inline_keyboard": [[
                        btn("پاسخ", f"ticket_reply_start:{t['id']}", "reply"),
                        btn("بستن", f"ticket_close:{t['id']}", "lock"),
                    ], [btn("پروفایل", f"admin_wuser:{t['user_id']}:n", "users")]]
                }
                tg.send_message(from_chat_id, caption, reply_markup=kb)
        return

    if (data in ("admin_wallet_start", "admin_msguser_start", "wallet_cancel", "wallet_apply:0", "wallet_apply:1")
            or data.startswith(("wallet_adj:", "wallet_revtop:", "admin_wrec:", "admin_wuser:", "admin_wtx:", "admin_wtxs:"))):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        _handle_admin_wallet_callback(cq, data)
        return

    if data.startswith("bc_seg:") or data == "bc_change_seg":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        state = db.get_admin_state(from_chat_id)
        if not state or state.get("action") not in ("await_broadcast_segment", "await_broadcast_confirm"):
            tg.answer_callback_query(cq_id, "این درخواست منقضی شده یا قبلاً انجام شده.", show_alert=True)
            return
        message_id = (state.get("data") or {}).get("message_id")
        tg.answer_callback_query(cq_id)
        if data == "bc_change_seg":
            db.set_admin_state(from_chat_id, "await_broadcast_segment", None, {"message_id": message_id})
            tg.send_message(
                from_chat_id,
                "پیام را برای کدام دسته ارسال کنم؟ (عدد کنار هر دسته، تعداد مخاطبان است)",
                reply_markup=broadcast_segment_keyboard(),
            )
            return
        segment = data.split(":", 1)[1]
        if segment not in BROADCAST_SEGMENT_LABELS:
            return
        db.set_admin_state(from_chat_id, "await_broadcast_confirm", None, {"message_id": message_id, "segment": segment})
        count = len([u for u in db.list_user_ids_by_segment(segment) if u != from_chat_id])
        tg.send_message(
            from_chat_id,
            f"پیام بالا دقیقاً همین‌طور برای دسته‌ی <b>{BROADCAST_SEGMENT_LABELS[segment]}</b> "
            f"(<b>{count} کاربر</b>) ارسال می‌شود. مطمئنید؟",
            reply_markup=broadcast_confirm_keyboard(segment),
        )
        return

    if data == "broadcast_confirm":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        state = db.get_admin_state(from_chat_id)
        if not state or state.get("action") != "await_broadcast_confirm":
            tg.answer_callback_query(cq_id, "این درخواست منقضی شده یا قبلاً انجام شده.", show_alert=True)
            return
        message_id = (state.get("data") or {}).get("message_id")
        segment = (state.get("data") or {}).get("segment", "all")
        # قبل از شروعِ (احتمالاً طولانیِ) حلقه‌ی ارسال، وضعیت را پاک می‌کنیم تا اگر
        # تلگرام همین کالبک را دوباره فرستاد (مثلاً به‌خاطر تاخیر پاسخ)، دوباره ارسال نشود.
        db.clear_admin_state(from_chat_id)
        tg.answer_callback_query(cq_id, "شروع ارسال...")
        tg.send_message(
            from_chat_id,
            f"⏳ در حال ارسال پیام برای «{BROADCAST_SEGMENT_LABELS.get(segment, segment)}»... "
            f"(بسته به تعداد کاربران ممکن است کمی طول بکشد)",
        )
        success, fail = _run_broadcast(from_chat_id, message_id, segment)
        tg.send_message(
            from_chat_id,
            f"📢 پیام همگانی ({BROADCAST_SEGMENT_LABELS.get(segment, segment)}) تمام شد.\n"
            f"✅ ارسال موفق: {success}\n❌ ناموفق (مثلاً کاربر ربات را بلاک کرده): {fail}",
        )
        return

    if data == "broadcast_cancel":
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        db.clear_admin_state(from_chat_id)
        tg.answer_callback_query(cq_id, "لغو شد.")
        show_screen(from_chat_id, message_id, "پیام همگانی لغو شد.")
        return

    if data.startswith("delplan_ask:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        plan_id = int(data.split(":", 1)[1])
        plan = db.get_plan(plan_id)
        if not plan:
            tg.answer_callback_query(cq_id, "پلن پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"⚠️ آیا مطمئنید می‌خواهید پلن «{plan['name']}» را کامل و برای همیشه حذف کنید؟\n"
            f"این کار قابل بازگشت نیست (سفارش‌های قبلی این پلن دست‌نخورده می‌مانند).",
            reply_markup={
                "inline_keyboard": [[
                    {"text": "❗️بله، کامل حذف کن", "callback_data": f"delplan_confirm:{plan_id}"},
                    {"text": "انصراف", "callback_data": "delplan_cancel"},
                ]]
            },
        )
        return

    if data.startswith("delplan_confirm:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        plan_id = int(data.split(":", 1)[1])
        plan = db.get_plan(plan_id)
        db.delete_plan(plan_id)
        tg.answer_callback_query(cq_id, "حذف شد.")
        name = plan["name"] if plan else f"#{plan_id}"
        show_screen(from_chat_id, message_id, f"🗑 پلن «{name}» برای همیشه حذف شد.")
        return

    if data == "delplan_cancel":
        tg.answer_callback_query(cq_id, "انصراف داده شد.")
        return

    if data.startswith("toggle_plan:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        _, plan_id_str, new_state = data.split(":", 2)
        plan_id = int(plan_id_str)
        if new_state == "off":
            db.deactivate_plan(plan_id)
        else:
            db.activate_plan(plan_id)
        plan = db.get_plan(plan_id)
        tg.answer_callback_query(cq_id, "به‌روزرسانی شد.")
        if plan and message_id:
            state_label = "🟢 فعال" if plan["active"] else "🔴 غیرفعال"
            caption = f"#{plan['id']} - {plan['name']}\n{plan['price']:,} تومان - {state_label}"
            if plan.get("description"):
                caption += f"\n{plan['description']}"
            tg.edit_message_text(from_chat_id, message_id, caption, reply_markup=plan_row_keyboard(plan))
        return

    if data.startswith("editplan_start:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        plan_id = int(data.split(":", 1)[1])
        plan = db.get_plan(plan_id)
        if not plan:
            tg.answer_callback_query(cq_id, "پلن پیدا نشد.", show_alert=True)
            return
        db.set_admin_state(from_chat_id, "await_edit_plan", None, {"plan_id": plan_id})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            from_chat_id,
            f"در حال ویرایش «{plan['name']}».\n\n"
            f"مقدار جدید را به این فرمت بفرستید:\nنام | قیمت | توضیح\n\n"
            f"برای رد کردن (نگه‌داشتن) یک فیلد، به‌جایش - بگذارید.\n"
            f"مقدار فعلی: {plan['name']} | {plan['price']} | {plan.get('description') or '-'}\n\n"
            f"(برای انصراف، /cancel را بزنید)",
        )
        return

    if data.startswith("finish_delivery:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        order_id = int(data.split(":", 1)[1])
        done = _finish_delivery(from_chat_id, expected_order_id=order_id, announce=False)
        tg.answer_callback_query(cq_id, "ثبت شد." if done else "این سفارش قبلاً بسته شده.")
        if done:
            _close_clicked_message(cq, order_id, f"✅ سفارش بسته شد ({done['count']} فایل فرستاده شد)")
            _notify_other_admins(order_id, from_chat_id, "📦 تحویل کامل شد", cq["from"], final=True)
        elif message_id:
            tg.edit_message_reply_markup(from_chat_id, message_id)
        return

    if data.startswith("approve:") or data.startswith("reject:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return

        _parts = data.split(":")
        action = _parts[0]
        order_id = int(_parts[1])
        reject_reason = _parts[2] if len(_parts) > 2 else None
        order = db.get_order(order_id)
        if not order:
            tg.answer_callback_query(cq_id, "سفارش پیدا نشد.", show_alert=True)
            return

        # -------- جلوگیری از بررسی دوباره توسط یک ادمین دیگر --------
        # اگر وضعیت سفارش دیگر awaiting_review نیست، یعنی یک ادمین دیگر (یا خود همین ادمین
        # از یک پیام دیگر) قبلاً روی آن تصمیم گرفته. به‌جای پردازش دوباره، فقط خبر می‌دهیم.
        if order["status"] != "awaiting_review":
            status_fa = {
                "approved": "قبلاً تایید شده (منتظر ارسال فایل یا در حال تحویل)",
                "rejected": "قبلاً رد شده",
                "delivered": "قبلاً تایید و تحویل داده شده",
            }.get(order["status"], order["status"])
            tg.answer_callback_query(cq_id, f"⚠️ این سفارش {status_fa}.", show_alert=True)
            if message_id:
                tg.edit_message_reply_markup(from_chat_id, message_id)  # پیام عکس/فایل را هم پوشش می‌دهد
            return

        if action == "approve" and order.get("order_type") == "wallet_topup":
            # سفارش شارژ کیف پول: نیازی به ارسال فایل نیست، فقط موجودی شارژ می‌شود.
            # قفل اتمیک: اگر دو ادمین هم‌زمان تایید بزنند، فقط یکی شارژ را انجام می‌دهد (شارژ دوبل نمی‌شود)
            if not db.claim_order(order_id, "awaiting_review", "delivered"):
                tg.answer_callback_query(cq_id, "این سفارش هم‌زمان توسط ادمین دیگری رسیدگی شد.", show_alert=True)
                return
            try:
                new_balance = db.add_user_balance(
                    order["user_id"], order["price"], kind="topup", order_id=order_id,
                )
            except Exception:
                db.set_order_status(order_id, "awaiting_review")
                raise
            bonus = topup_bonus_for(order["price"])
            if bonus:
                db.add_topup_bonus(order["user_id"], bonus, order_id=order_id)
            tg.answer_callback_query(cq_id, "تایید و کیف پول شارژ شد.")
            bonus_parts = [f"🎁 هدیه‌ی شارژ: {bonus:,} تومان (فقط برای خرید پلن/کانفیگ)\n"] if bonus else []
            send_rich(
                order["user_id"],
                [
                    ("e", "check"), " رسید شما تایید شد و کیف پول‌تان به مبلغ ", ("b", f"{order['price']:,} تومان"), " شارژ شد.\n",
                    f"موجودی جدید: {new_balance:,} تومان\n",
                ] + bonus_parts + ["\nبا تشکر از شما 🙏"],
            )
            _clear_wait_message(order)
            done_note = "✅ تایید شد و کیف پول کاربر شارژ شد" + (f" (+{bonus:,} هدیه)" if bonus else "")
            _close_clicked_message(cq, order_id, done_note)
            _notify_other_admins(order_id, from_chat_id, done_note, cq["from"], final=True)
        elif action == "approve" and order.get("order_type") == "uc":
            # سفارش یوسی: نیازی به ارسال فایل نیست؛ ادمین بعداً دستی UC را واریز می‌کند
            # و با دکمه‌ی «واریز انجام شد» تکمیل سفارش را ثبت می‌کند.
            db.set_order_status(order_id, "approved")
            tg.answer_callback_query(cq_id, "تایید شد.")
            _close_clicked_message(cq, order_id, "✅ تایید شد")
            tg.send_message(
                from_chat_id,
                f"✅ سفارش #{order_id} تایید شد.\n"
                f"🎯 برای واریز: {html.escape(order['plan_name'] or '')}\n"
                f"{uc_total_line(order)}\n"
                f"{pubg_id_mono_block(order)}\n\n"
                f"بعد از واریز دستی UC، دکمه‌ی زیر را بزنید.",
                reply_markup={"inline_keyboard": [[btn("واریز انجام شد", f"uc_deliver:{order_id}", "check")]]},
            )
            send_rich(
                order["user_id"],
                [
                    ("e", "check"), f" رسید سفارش #{order_id} شما تایید شد.\n",
                    f"لطفاً منتظر بمانید تا «{order['plan_name']}» به اکانت پابجی شما واریز شود؛ "
                    f"به محض واریز، پیام تاییدیه برای شما ارسال خواهد شد.",
                ],
            )
            _notify_other_admins(order_id, from_chat_id, "✅ تایید شد (منتظر واریز UC)", cq["from"])
            if not order.get("pubg_info"):
                # کاربر هنوز آیدی عددی نفرستاده: هم به ادمین هشدار می‌دهیم و هم دوباره از کاربر می‌خواهیم
                tg.send_message(
                    from_chat_id,
                    f"⚠️ توجه: آیدی عددی پابجی سفارش #{order_id} هنوز از کاربر دریافت نشده. "
                    f"یک یادآوری فوری برای کاربر ارسال شد؛ تا رسیدن آیدی، واریز نکنید.",
                )
                _ask_pubg_info(order["user_id"], order_id, reminder=True)
        elif action == "approve":
            # اگر قبلاً منتظر فایل یک سفارش دیگر بودیم و ادمین آن را نفرستاده، هشدار بده
            # چون این حالت با تایید سفارش جدید بازنویسی می‌شود و آن سفارش قبلی بدون کانفیگ می‌ماند.
            prev_state = db.get_admin_state(from_chat_id)
            if prev_state and prev_state.get("action") == "await_config_file" and prev_state.get("order_id") != order_id:
                tg.send_message(
                    from_chat_id,
                    f"⚠️ توجه: هنوز منتظر فایل کانفیگ سفارش #{prev_state['order_id']} بودم و فایلی دریافت نشده بود.\n"
                    f"اگر فایل آن را نمی‌فرستید، بعداً با /pending دوباره می‌توانید آن را تایید کنید تا حالت انتظار برایش فعال شود.",
                )

            db.set_order_status(order_id, "approved")
            tg.answer_callback_query(cq_id, "تایید شد.")
            _close_clicked_message(cq, order_id, "✅ تایید شد")
            resp = tg.send_message(
                from_chat_id,
                f"✅ سفارش #{order_id} تایید شد.\n"
                f"حالا فایل(های) کانفیگ را همینجا برای من بفرستید (می‌توانید چند فایل پشت‌سرهم بفرستید).\n"
                f"وقتی همه را فرستادید، دکمه‌ی «✅ پایان» که بعد از هر فایل نشان داده می‌شود را بزنید.",
            )
            db.set_admin_state(from_chat_id, "await_config_file", order_id, {"sent_count": 0, "msg_id": _msg_id(resp)})
            _notify_other_admins(order_id, from_chat_id, "✅ تایید شد (در حال ارسال فایل)", cq["from"])
        else:
            db.set_order_status(order_id, "rejected")
            tg.answer_callback_query(cq_id, "رد شد.")
            if reject_reason == "kyc":
                user_parts = [
                    ("e", "cross"), f" متاسفانه ", ("b", "احراز هویت"), f" شما برای سفارش #{order_id} تایید نشد.\n",
                    "دلیل: مدارک ارسالی (عکس کارت بانکی / کارت ملی) ناقص، نامعتبر یا مطابق با صاحب کارتِ پرداخت‌کننده نبود.\n\n"
                    "لطفاً با پشتیبانی در ارتباط باشید.",
                ]
                admin_note = "رد احراز هویت"
                other_note = "❌ رد شد (رد احراز هویت)"
            elif reject_reason == "rcpt":
                user_parts = [
                    ("e", "cross"), f" متاسفانه ", ("b", "رسید"), f" سفارش #{order_id} شما تایید نشد.\n",
                    "دلیل: رسید ارسالی نامعتبر، ناخوانا یا مغایر با پرداخت بود.\n\n"
                    "لطفاً با پشتیبانی در ارتباط باشید یا مجدد اقدام کنید.",
                ]
                admin_note = "رد رسید"
                other_note = "❌ رد شد (رد رسید)"
            else:
                user_parts = [("e", "cross"), f" متاسفانه رسید سفارش #{order_id} شما تایید نشد. لطفاً با پشتیبانی در ارتباط باشید یا مجدد اقدام کنید."]
                admin_note = "رد شد"
                other_note = "❌ رد شد"
            send_rich(order["user_id"], user_parts)
            _clear_wait_message(order)
            _close_clicked_message(cq, order_id, other_note)
            _notify_other_admins(order_id, from_chat_id, other_note, cq["from"], final=True)
        # (دکمه‌های همه‌ی ادمین‌ها با ویرایش همان کارت اصلی برداشته شد؛ پیام جدیدی لازم نیست)
        return

    if data.startswith("uc_deliver:"):
        if not is_admin(from_chat_id):
            tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
            return
        order_id = int(data.split(":", 1)[1])
        order = db.get_order(order_id)
        if not order:
            tg.answer_callback_query(cq_id, "سفارش پیدا نشد.", show_alert=True)
            return
        if order["status"] != "approved":
            status_fa = {
                "delivered": "قبلاً واریز و تکمیل شده",
                "rejected": "رد شده",
                "pending_receipt": "هنوز در انتظار رسید یا پرداخت است",
                "awaiting_review": "هنوز در انتظار بررسی رسید است",
            }.get(order["status"], order["status"])
            tg.answer_callback_query(cq_id, f"⚠️ این سفارش {status_fa}.", show_alert=True)
            if message_id:
                tg.edit_message_text(
                    from_chat_id, message_id,
                    f"سفارش #{order_id} — وضعیت فعلی: {status_fa}\n(توسط ادمین دیگری رسیدگی شده)",
                )
            return

        db.set_order_status(order_id, "delivered")
        tg.answer_callback_query(cq_id, "ثبت شد.")
        _clear_wait_message(order)
        _close_clicked_message(cq, order_id, "✅ واریز ثبت شد")
        done_parts = [("e", "check"), " ", ("b", "واریز با موفقیت انجام شد!"), "\n\n",
                      f"📦 سفارش: {order['plan_name']}\n"]
        total_uc = uc_total_from_name(order.get("plan_name"))
        if total_uc:
            done_parts += [("e", "uc"), " مجموع یوسی واریزشده: ", ("b", f"{total_uc:,} UC"), "\n"]
        delivered_id = extract_pubg_id(order.get("pubg_info") or "")
        if delivered_id:
            done_parts += ["🔢 آیدی عددی: ", ("code", delivered_id), "\n"]
        done_parts += ["\nلطفاً داخل بازی موجودی UC را بررسی کنید. با تشکر از خرید شما 🙏"]
        send_rich(order["user_id"], done_parts)
        _notify_other_admins(order_id, from_chat_id, "💳 واریز UC انجام شد", cq["from"], final=True)
        return

    tg.answer_callback_query(cq_id)


def _admin_label(admin_id, from_user=None):
    """نام قابل‌نمایش یک ادمین برای پیام‌های اطلاع‌رسانی بین ادمین‌ها."""
    fu = from_user or {}
    if fu.get("username"):
        return "@" + fu["username"]
    name = (fu.get("first_name", "") + " " + fu.get("last_name", "")).strip()
    return name or str(admin_id)


def _notify_other_admins_ticket(ticket_id, acting_admin_id, from_user, action_text, body=None, with_buttons=True):
    """وقتی یک ادمین به تیکتی پاسخ می‌دهد یا آن را می‌بندد، به بقیه‌ی ادمین‌ها خبر می‌دهد."""
    who = _admin_label(acting_admin_id, from_user)
    parts = [("e", "ticket"), " ", ("b", f"تیکت #{ticket_id}"), f" — ادمین {who} {action_text}.\n"]
    if body:
        snippet = body if len(body) <= 300 else body[:300] + "…"
        parts += ["\n💬 ", snippet, "\n"]
    parts.append("\nنیازی به اقدام شما نیست.")
    text, entities = build_message(parts)
    kb = None
    if with_buttons:
        ticket = db.get_ticket(ticket_id) or {}
        rows = [[
            btn("پاسخ", f"ticket_reply_start:{ticket_id}", "reply"),
            btn("بستن تیکت", f"ticket_close:{ticket_id}", "lock"),
        ]]
        if ticket.get("user_id"):
            rows.append([btn("پروفایل", f"admin_wuser:{ticket['user_id']}:n", "users")])
        kb = {"inline_keyboard": rows}
    for admin_id in ADMIN_IDS:
        if admin_id == acting_admin_id:
            continue
        tg.send_message(admin_id, text, reply_markup=kb, entities=entities)


def _compose_card(base, status, limit):
    """متن اصلی کارت + خط‌های وضعیت؛ اگر از سقف تلگرام بلندتر شد، از انتهای متن اصلی کوتاه می‌شود."""
    sep = "\n\n"
    if len(base) + len(sep) + len(status) > limit:
        base = base[: max(0, limit - len(sep) - len(status) - 1)] + "…"
    return base + sep + status


def _edit_review_card(card, status_line):
    """یک کارت بررسی سفارش را ویرایش می‌کند: متن اصلی + خط‌های وضعیت، و دکمه‌ها برداشته می‌شوند.
    False یعنی کارت قدیمی بوده (متن اصلی ثبت نشده) و فقط دکمه‌هایش برداشته شد."""
    admin_id, message_id = card["admin_id"], card["message_id"]
    base = card.get("base_text") or ""
    if not base:
        try:
            tg.edit_message_reply_markup(admin_id, message_id)
        except Exception:
            pass
        return False
    status = card.get("status_text") or status_line
    try:
        if card.get("kind") == "caption":
            tg.edit_message_caption(admin_id, message_id, _compose_card(base, status, tg.CAPTION_LIMIT))
        else:
            tg.edit_message_text(admin_id, message_id, _compose_card(base, status, 4000),
                                 reply_markup=EMPTY_KB, parse_mode=None)
    except Exception:
        logger.exception("edit_review_card failed")
    return True


def _notify_other_admins(order_id, acting_admin_id, status, from_user=None, final=False):
    """به‌جای فرستادن پیام جدید به بقیه‌ی ادمین‌ها، همان کارت اصلی سفارش را برای همه‌ی ادمین‌ها
    ویرایش می‌کند: متن اصلی می‌ماند، زیرش «<status> — توسط @فلانی» اضافه می‌شود و دکمه‌ها برداشته می‌شوند.
    final=True یعنی کار این سفارش تمام شده و کارت‌ها از جدول پاک می‌شوند.
    فقط اگر هیچ کارتی ثبت نشده باشد (سفارش‌های قدیمی)، همان پیام اطلاع‌رسانی قبلی فرستاده می‌شود."""
    who = _admin_label(acting_admin_id, from_user)
    line = f"{status} — توسط {who}"
    try:
        db.append_review_status(order_id, line)
        cards = db.list_review_cards(order_id)
    except Exception:
        logger.exception("review cards update failed")
        cards = []

    legacy_admins = set()
    for card in cards:
        if not _edit_review_card(card, line):
            legacy_admins.add(card["admin_id"])
    if final and cards:
        try:
            db.delete_review_cards(order_id)
        except Exception:
            logger.exception("delete_review_cards failed")

    if not cards:
        targets = [a for a in ADMIN_IDS if a != acting_admin_id]
    else:
        targets = [a for a in legacy_admins if a != acting_admin_id]
    if targets:
        text, entities = build_message([("e", "info"), f" سفارش #{order_id}: {line}\nنیازی به اقدام شما نیست."])
        for admin_id in targets:
            tg.send_message(admin_id, text, entities=entities)


def _close_clicked_message(cq, order_id, status_line):
    """پیامی را که ادمین روی دکمه‌اش زده (و کارت ثبت‌شده‌ی سفارش نیست، مثل پیام‌های /pending) با یک خط
    وضعیت می‌بندد و دکمه‌هایش را برمی‌دارد؛ به‌جای فرستادن یک پیام تاییدیه‌ی جدا.
    باید قبل از _notify_other_admins(final=True) صدا زده شود."""
    msg = cq.get("message") or {}
    mid = msg.get("message_id")
    chat_id = cq["from"]["id"]
    if not mid:
        return
    try:
        if mid in {c["message_id"] for c in db.list_review_cards(order_id) if c["admin_id"] == chat_id}:
            return  # کارت ثبت‌شده است و با _notify_other_admins ویرایش می‌شود
    except Exception:
        pass
    if msg.get("text") is not None:
        tg.edit_message_text(chat_id, mid, _compose_card(msg["text"], status_line, 4000),
                             reply_markup=EMPTY_KB, parse_mode=None)
    elif msg.get("caption") is not None:
        tg.edit_message_caption(chat_id, mid, _compose_card(msg["caption"], status_line, tg.CAPTION_LIMIT))
    else:
        tg.edit_message_reply_markup(chat_id, mid)


def _track_review_card(order_id, admin_id, resp, kind="caption", base_text=""):
    """پیامی که دکمه‌ی تایید/رد دارد را (با متن اصلی‌اش) ثبت می‌کند تا بعد از تصمیم، همان کارت ویرایش شود."""
    try:
        if resp and resp.get("ok"):
            db.add_review_card(order_id, admin_id, resp["result"]["message_id"], kind, base_text)
    except Exception:
        logger.exception("track_review_card failed")


def _send_review_card(admin_id, order, caption, keyboard=None):
    """کارت بررسی سفارش برای یک ادمین: رسید + کپشن + دکمه‌ها در «یک» پیام (نه فوروارد و پیام جدا).
    اگر ارسال رسید ناموفق بود (مثلاً file_id نامعتبر)، همان کپشن به‌صورت پیام متنی فرستاده می‌شود."""
    file_id = order.get("receipt_file_id")
    resp, kind = None, "text"
    if file_id:
        rtype = order.get("receipt_type") if order.get("receipt_type") in ("photo", "document") else "photo"
        resp = tg.send_media_by_file_id(admin_id, rtype, file_id, caption=caption, reply_markup=keyboard)
        kind = "caption"
    if not (resp and resp.get("ok")):
        resp = tg.send_message(admin_id, caption, reply_markup=keyboard, parse_mode=None)
        kind = "text"
    if keyboard:
        _track_review_card(order["id"], admin_id, resp, kind, caption)
    return resp


# ---------------------------------------------------------------------------
# پردازش عکس (رسید پرداخت)
# ---------------------------------------------------------------------------

KYC_MIN_PHOTOS = 2


def _kyc_confirm_keyboard(order_id):
    return {"inline_keyboard": [[btn("تایید و ارسال مدارک", f"kyc_confirm:{order_id}", "check")]]}


def _send_kyc_prompt(chat_id, order):
    send_rich(
        chat_id,
        [
            ("e", "card"), " ", ("b", "رسید شما دریافت شد."), "\n\n",
            f"مبلغ این پرداخت از {get_kyc_threshold():,} تومان بیشتر است؛ برای احراز هویت و جلوگیری از سوءاستفاده، "
            "لطفاً ", ("b", "همین الان"), " همین‌جا این ", ("b", "دو عکس"), " را بفرستید:\n",
            ("e", "num1"), " عکس کارت بانکی\n",
            ("e", "num2"), " عکس کارت ملی\n\n",
            "هر دو باید متعلق به همان شخصی باشد که با کارتش خرید/واریز را انجام داده است.\n",
            ("e", "forbid"), " تا زمانی که مدارک ارسال نشود، سفارش شما بررسی نخواهد شد.\n\n",
            "بعد از ارسال هر دو عکس، دکمه‌ی «تایید و ارسال مدارک» را بزنید.",
        ],
        reply_markup=_kyc_confirm_keyboard(order["id"]),
    )


def _handle_kyc_upload(message, from_user, order):
    """یکی از عکس‌های احراز هویت (کارت بانکی / کارت ملی) را ذخیره می‌کند."""
    chat_id = message["chat"]["id"]
    photo_sizes = message.get("photo")
    if not photo_sizes:
        send_rich(chat_id, ["📷 مدارک احراز هویت را باید به‌صورت ", ("b", "عکس"), " بفرستید (نه فایل)."])
        return
    count = db.add_kyc_file(order["id"], photo_sizes[-1]["file_id"], photo_sizes[-1].get("file_unique_id"))
    if count >= KYC_MIN_PHOTOS:
        send_rich(
            chat_id,
            [("e", "check"), f" {count} عکس دریافت شد.\nاگر مدارک کامل است (کارت بانکی + کارت ملی)، دکمه‌ی زیر را بزنید."],
            reply_markup=_kyc_confirm_keyboard(order["id"]),
        )
    else:
        _ack_message(chat_id, message.get("message_id"),
                     f"📷 عکس {count} از {KYC_MIN_PHOTOS} دریافت شد. لطفاً عکس بعدی را هم همین‌جا بفرستید.")


def _send_kyc_files_to_admin(admin_id, order):
    files = db.get_kyc_files(order["id"])
    if not files:
        return
    tg.send_message(admin_id, f"🪪 مدارک احراز هویت سفارش #{order['id']} ({len(files)} عکس):")
    for i, (fid, _u) in enumerate(files, 1):
        tg.send_photo_by_file_id(admin_id, fid, caption=f"مدرک {i} — سفارش #{order['id']}")


def _handle_kyc_confirm(cq, data):
    cq_id = cq["id"]
    from_chat_id = cq["from"]["id"]
    try:
        order_id = int(data.split(":", 1)[1])
    except ValueError:
        tg.answer_callback_query(cq_id)
        return
    order = db.get_order(order_id)
    if not order or order["user_id"] != from_chat_id:
        tg.answer_callback_query(cq_id, "این سفارش برای شما نیست.", show_alert=True)
        return
    if order["status"] != "awaiting_kyc":
        tg.answer_callback_query(cq_id, "مدارک این سفارش قبلاً ارسال شده است.", show_alert=True)
        return
    files = db.get_kyc_files(order_id)
    if len(files) < KYC_MIN_PHOTOS:
        tg.answer_callback_query(
            cq_id,
            f"⛔ هنوز هر دو عکس (کارت بانکی و کارت ملی) را نفرستاده‌اید ({len(files)} از {KYC_MIN_PHOTOS}). "
            f"تا ارسال هر دو، سفارش #{order_id} بررسی نمی‌شود؛ لطفاً مدارک را همین الان بفرستید.",
            show_alert=True,
        )
        return
    if not db.claim_order(order_id, "awaiting_kyc", "awaiting_review"):
        tg.answer_callback_query(cq_id, "مدارک این سفارش قبلاً ارسال شده است.", show_alert=True)
        return
    tg.answer_callback_query(cq_id)
    show_rich(
        from_chat_id, (cq.get("message") or {}).get("message_id"),
        [
            ("e", "check"), " مدارک شما دریافت شد.\n\n",
            "اکنون منتظر ", ("b", "تایید رسید و احراز هویت"), " توسط پشتیبانی باشید؛ نتیجه همین‌جا اطلاع داده می‌شود.",
        ],
    )

    fresh = db.get_order(order_id)
    caption = _receipt_caption(fresh, cq["from"]) + "\n\n🪪 مدارک احراز هویت دریافت شد؛ رسید و مدارک را بررسی کنید."
    keyboard = admin_review_keyboard(fresh)
    for admin_id in ADMIN_IDS:
        _send_kyc_files_to_admin(admin_id, fresh)
        _send_review_card(admin_id, fresh, caption, keyboard)

    if fresh.get("order_type") == "uc":
        _ask_pubg_info(from_chat_id, order_id)


def _receipt_caption(order, from_user):
    """متن کپشن سفارش برای ادمین‌ها (کاربر، نوع سفارش، مبلغ و ...)."""
    price_line = f"پلن: {order['plan_name']} - {order['price']:,} تومان"
    if order.get("order_type") == "wallet_topup":
        price_line = f"👛 شارژ کیف پول\nمبلغ: {order['price']:,} تومان"
    elif order.get("discount_code"):
        price_line = (
            f"پلن: {order['plan_name']}\n"
            f"قیمت اصلی: {order.get('original_price') or order['price']:,} تومان\n"
            f"کد تخفیف: {order['discount_code']} (-{order.get('discount_amount') or 0:,} تومان)\n"
            f"مبلغ نهایی: {order['price']:,} تومان"
        )
    elif order.get("order_type") == "uc":
        rate_at_order = order.get("usdt_rate")
        current_rate = get_usdt_price()
        rate_line = f"نرخ تتر هنگام ثبت سفارش: {rate_at_order:,.0f} تومان\n" if rate_at_order else ""
        if current_rate:
            rate_line += f"نرخ تتر لحظه‌ی الان: {current_rate:,.0f} تومان\n"
        else:
            rate_line += "نرخ تتر الان: (دریافت ناموفق بود)\n"
        price_line = f"🎯 آیتم یوسی: {order['plan_name']}\n{rate_line}مبلغ سفارش: {order['price']:,} تومان"

    caption = (
        f"🧾 رسید جدید - سفارش #{order['id']}\n"
        f"کاربر: {from_user.get('first_name', '')} {from_user.get('last_name', '')} "
        f"(@{from_user.get('username', '-')}) | آیدی: {from_user['id']}\n"
        f"{price_line}"
    )
    return caption


def _send_receipt_received(chat_id, order):
    """پیام بعد از دریافت رسید: تاکید روی صبر تا انجام کار (تایید رسید + واریز/شارژ/تحویل) و تیکت‌نزدن؛
    بعدش یک پیام جدا فقط با ایموجی پریمیومِ انیمیشنی تا تکی و بزرگ نمایش داده شود."""
    kind = order.get("order_type") or "plan"
    if kind == "uc":
        steps = ["تایید رسید", " و ", "واریز یوسی به اکانت پابجی شما"]
    elif kind == "wallet_topup":
        steps = ["تایید رسید", " و ", "شارژ کیف پول"]
    else:
        steps = ["تایید رسید", " و ", "تحویل اشتراک شما"]
    parts = [
        ("e", "check"), " ", ("b", "رسید شما دریافت شد"), " و در صف بررسی ادمین قرار گرفت.\n\n",
        "⏳ لطفاً صبور باشید تا ادمین ", ("b", steps[0]), steps[1], ("b", steps[2]),
        " را انجام دهد؛ نتیجه همین‌جا به شما اطلاع داده می‌شود.\n\n",
        "🙏 در این مدت ", ("b", "تیکت نزنید"), " و رسید را دوباره نفرستید؛ پیام یا تیکت تکراری فقط باعث کندترشدن "
        "رسیدگی می‌شود. سفارش شما به‌ترتیب و در اولین فرصت انجام می‌شود.",
    ]
    text, entities = build_message(parts)
    recv = tg.send_message(chat_id, text, entities=entities)
    db.set_order_msg(order["id"], "recv_msg_id", _msg_id(recv))
    # پیامی که فقط یک ایموجی پریمیوم دارد، بزرگ و تمام‌صفحه (انیمیشنی) نمایش داده می‌شود
    emoji_text, emoji_entities = build_message([("e", "wait")])
    resp = tg.send_message(chat_id, emoji_text, entities=emoji_entities)
    db.set_order_wait_msg(order["id"], _msg_id(resp))  # بعد از انجام کار (تایید/واریز/تحویل/رد/لغو) پاک می‌شود


def _clear_wait_message(order):
    """بعد از انجام کار، پیام‌های انتظار این سفارش («رسید دریافت شد» + ایموجی ⏳) را از چت مشتری پاک می‌کند،
    تا فقط پیام نتیجه بماند. (ویرایش پیام برای مشتری نوتیفیکیشن نمی‌فرستد؛ برای همین پاک و پیام نتیجه تازه فرستاده می‌شود.)"""
    for field in ("wait_msg_id", "recv_msg_id"):
        try:
            mid = db.pop_order_msg(order["id"], field)
            if mid:
                tg.delete_message(order["user_id"], mid)
        except Exception:
            logger.exception("clear_wait_message failed")


def _delete_pay_message(order):
    """پیام اطلاعات پرداخت (با دکمه‌ی لغو) بعد از ارسال رسید منقضی است؛ پاک می‌شود."""
    try:
        mid = db.pop_order_msg(order["id"], "pay_msg_id")
        if mid:
            tg.delete_message(order["user_id"], mid)
    except Exception:
        logger.exception("delete_pay_message failed")


def _process_receipt(message, from_user):
    """منطق مشترک برای دریافت رسید، چه به‌صورت عکس و چه به‌صورت فایل/سند."""
    chat_id = message["chat"]["id"]

    order = db.get_latest_open_order_for_user(from_user["id"])
    if order and _uc_order_is_stale(order):
        # مهلت قیمت تمام شده ولی پاکسازی دوره‌ای هنوز اجرا نشده بود
        _expire_uc_order_and_notify(order, chat_id)
        return
    if order and order["status"] == "awaiting_kyc":
        # رسید قبلاً ثبت شده؛ این پیام یکی از عکس‌های احراز هویت است
        _handle_kyc_upload(message, from_user, order)
        return
    if not order or order["status"] != "pending_receipt":
        if order and order["status"] == "awaiting_review":
            tg.send_message(chat_id, "رسید قبلی شما در حال بررسی است، لطفاً منتظر بمانید.")
        elif order and order["status"] == "expired":
            tg.send_message(
                chat_id,
                f"⏰ مهلت پرداخت سفارش #{order['id']} به پایان رسیده و قیمت آن دیگر معتبر نیست.\n"
                f"این رسید تایید نخواهد شد.\n\n"
                f"👇 برای دریافت قیمت جدید و ثبت مجدد همین سفارش، دکمه‌ی زیر را بزنید.",
                reply_markup=_uc_requote_keyboard(order["id"]),
            )
        else:
            tg.send_message(
                chat_id,
                "سفارشی در انتظار پرداخت ندارید. برای خرید، از /start شروع کنید.\n\n"
                "اگر برای سفارشی که لغو یا منقضی شده واریز انجام داده‌اید، لطفاً از «ارتباط با ما» "
                "یک تیکت باز کنید و رسید را همان‌جا بفرستید تا پیگیری شود.",
            )
        return

    if "photo" in message:
        photo_sizes = message.get("photo", [])
        if not photo_sizes:
            return
        file_id = photo_sizes[-1]["file_id"]  # بزرگترین سایز
        unique_id = photo_sizes[-1].get("file_unique_id")
        receipt_type = "photo"
    elif "document" in message:
        file_id = message["document"]["file_id"]
        unique_id = message["document"].get("file_unique_id")
        receipt_type = "document"
    else:
        return

    # -------- تشخیص رسید تکراری: به درخواست شما غیرفعال شد (باعث رد اشتباهی رسیدهای واقعی --------
    # -------- و متفاوت می‌شد). unique_id همچنان ذخیره می‌شود تا اگر خواستید دستی بررسی کنید. --------
    # dup = db.find_duplicate_receipt(unique_id, order["id"])
    # if dup:
    #     tg.send_message(
    #         chat_id,
    #         "❌ این رسید قبلاً برای یک سفارش دیگر ثبت شده و برای این سفارش قابل استفاده نیست.\n\n"
    #         "لطفاً رسید همین واریز را بفرستید. اگر فکر می‌کنید اشتباهی رخ داده، از «ارتباط با ما» تیکت بزنید.",
    #     )
    #     for admin_id in ADMIN_IDS:
    #         tg.send_message(
    #             admin_id,
    #             f"⚠️ رسید تکراری!\n"
    #             f"کاربر: {from_user.get('first_name', '')} (@{from_user.get('username', '-')}) | آیدی: {from_user['id']}\n"
    #             f"سفارش فعلی: #{order['id']}\n"
    #             f"همین فایل قبلاً برای سفارش #{dup['id']} (کاربر {dup['user_id']}، وضعیت: {dup['status']}) ثبت شده بود؛ "
    #             f"رد شد و برای سفارش فعلی ثبت نشد.",
    #         )
    #     return

    needs_kyc = kyc_needed(order)
    db.set_order_receipt(
        order["id"], file_id, receipt_type, unique_id,
        status="awaiting_kyc" if needs_kyc else "awaiting_review",
        kyc_required=needs_kyc,
    )

    _delete_pay_message(order)  # اطلاعات پرداخت و دکمه‌ی لغو بعد از ارسال رسید دیگر لازم نیست

    if needs_kyc:
        # مبلغ بالاتر از حد احراز هویت: تا ارسال مدارک، سفارش برای بررسی/تایید نمی‌رود
        _send_kyc_prompt(chat_id, order)
        caption = _receipt_caption(order, from_user) + (
            f"\n\n⏳ این پرداخت بیشتر از {get_kyc_threshold():,} تومان است؛ "
            f"منتظر دریافت احراز هویت (عکس کارت بانکی و کارت ملی) از کاربر هستم. "
            f"بعد از ارسال مدارک، دکمه‌های تایید/رد برایتان می‌آید."
        )
        card_order = dict(order, receipt_file_id=file_id, receipt_type=receipt_type)
        for admin_id in ADMIN_IDS:
            _send_review_card(admin_id, card_order, caption)  # بدون دکمه؛ دکمه‌ها بعد از ارسال مدارک می‌آید
        return

    _send_receipt_received(chat_id, order)

    # ارسال برای ادمین
    caption = _receipt_caption(order, from_user)
    card_order = dict(order, receipt_file_id=file_id, receipt_type=receipt_type)
    keyboard = admin_review_keyboard(order)
    for admin_id in ADMIN_IDS:
        _send_review_card(admin_id, card_order, caption, keyboard)  # یک پیام: رسید + کپشن + دکمه‌ها

    # -------- برای سفارش‌های یوسی، آیدی و نام اکانت پابجی را هم از کاربر می‌خواهیم --------
    if order.get("order_type") == "uc":
        _ask_pubg_info(chat_id, order["id"])


def handle_photo(message):
    chat_id = message["chat"]["id"]
    from_user = message.get("from", {})

    if is_admin(chat_id):
        state = db.get_admin_state(chat_id)
        if state and state.get("action") == "await_config_file":
            _deliver_one_file(chat_id, state, message, is_document=False)
            return
        if state and state.get("action") in ("await_ticket_reply", "await_support_msg"):
            _handle_ticket_admin_media(chat_id, message, state, from_user)
            return
        if state and state.get("action") == "await_tutorial_text":
            _handle_tutorial_media(chat_id, message, state)
            return
        # ادمین عکسی فرستاده ولی الان منتظر تحویل فایل هیچ سفارشی نیست
        tg.send_message(chat_id, "در حال حاضر منتظر ارسال فایلی نیستم. برای شروع تحویل یک سفارش، /pending را بزنید.")
        return

    if _try_user_ticket_media(message):
        return
    _process_receipt(message, from_user)


def _try_user_ticket_media(message):
    """اگر مشتری وسط نوشتن پیام تیکت است، مدیا (عکس/ویدیو/ویس/صوت/فایل) را به‌جای رسید، پیام تیکت حساب می‌کند."""
    chat_id = message["chat"]["id"]
    state = db.get_admin_state(chat_id)
    if not state or state.get("action") != "await_ticket_message":
        return False
    kind, file_id = _extract_media(message)
    if not file_id:
        return False
    caption = (message.get("caption") or "").strip()
    _handle_ticket_user_message(chat_id, caption, state, message.get("from", {}),
                                message.get("message_id"), media=(kind, file_id, caption))
    return True


def handle_other_media(message):
    """ویدیو، صوت و ویس: ادمین در حالت پاسخ به تیکت، یا مشتری در حالت نوشتن پیام تیکت؛ برای بقیه نادیده گرفته می‌شود."""
    chat_id = message["chat"]["id"]
    from_user = message.get("from", {})
    if not is_admin(chat_id):
        _try_user_ticket_media(message)
        return
    state = db.get_admin_state(chat_id)
    if state and state.get("action") in ("await_ticket_reply", "await_support_msg"):
        _handle_ticket_admin_media(chat_id, message, state, from_user)
        return
    if state and state.get("action") == "await_tutorial_text":
        _handle_tutorial_media(chat_id, message, state)
        return
    tg.send_message(
        chat_id,
        "در حال حاضر منتظر این نوع پیام نیستم. برای فرستادن ویدیو/صوت به کاربر، اول روی «پاسخ» زیر تیکت او بزنید.",
    )


# ---------------------------------------------------------------------------
# پردازش سند/فایل — یا رسید کاربر عادی، یا یکی از فایل‌های کانفیگ که ادمین
# برای تحویل به مشتری می‌فرستد (هر سفارش می‌تواند چند فایل داشته باشد)
# ---------------------------------------------------------------------------

def handle_document(message):
    chat_id = message["chat"]["id"]
    from_user = message.get("from", {})

    if is_admin(chat_id):
        state = db.get_admin_state(chat_id)
        if state and state.get("action") == "await_config_file":
            _deliver_one_file(chat_id, state, message, is_document=True)
            return
        if state and state.get("action") in ("await_ticket_reply", "await_support_msg"):
            _handle_ticket_admin_media(chat_id, message, state, from_user)
            return
        if state and state.get("action") == "await_tutorial_text":
            _handle_tutorial_media(chat_id, message, state)
            return
        tg.send_message(
            chat_id,
            "در حال حاضر منتظر فایلی برای ارسال نیستم.\n"
            "اگر این فایل کانفیگ یک مشتری است، ابتدا با /pending سفارش مربوطه را تایید یا انتخاب کنید.",
        )
        return

    if _try_user_ticket_media(message):
        return
    _process_receipt(message, from_user)


def _deliver_one_file(admin_chat_id, state, message, is_document):
    """یک فایل (سند یا عکس) را برای خریدارِ سفارشِ جاری ادمین می‌فرستد و شمارنده را افزایش می‌دهد."""
    order_id = state["order_id"]
    order = db.get_order(order_id)
    if not order:
        tg.send_message(admin_chat_id, "سفارش پیدا نشد.")
        db.clear_admin_state(admin_chat_id)
        return
    if order["status"] != "approved":
        tg.send_message(
            admin_chat_id,
            f"⚠️ سفارش #{order_id} دیگر در انتظار ارسال فایل نیست (وضعیت: {ORDER_STATUS_LABELS.get(order['status'], order['status'])}). فایل ارسال نشد.",
        )
        db.clear_admin_state(admin_chat_id)
        return

    if is_document:
        file_id = message["document"]["file_id"]
        file_name = message["document"].get("file_name") or "فایل"
        file_type = "document"
        tg.send_document_by_file_id(order["user_id"], file_id)
    else:
        photo_sizes = message.get("photo", [])
        if not photo_sizes:
            return
        file_id = photo_sizes[-1]["file_id"]
        file_name = (message.get("caption") or "").strip() or "عکس"
        file_type = "photo"
        tg.send_photo_by_file_id(order["user_id"], file_id)
    # ثبت نام و شناسه‌ی فایل تا ادمین بتواند بعداً در «پروفایل کاربر» ببیند چه کانفیگی برایش فرستاده شده
    db.log_delivered_file(order_id, order["user_id"], file_name, file_type, file_id)

    _clear_wait_message(order)  # مشتری فایلش را گرفته؛ ایموجی انتظار دیگر لازم نیست
    data = state.get("data") or {}
    count = data.get("sent_count", 0) + 1
    data["sent_count"] = count

    # فقط یک پیام وضعیت (همیشه پایین چت): پیام قبلیِ «فایل شماره N» پاک می‌شود تا چت پر از پیام تکراری نشود
    old_msg = data.get("msg_id")
    if old_msg:
        tg.delete_message(admin_chat_id, old_msg)
    resp = tg.send_message(
        admin_chat_id,
        f"📎 فایل شماره {count} برای سفارش #{order_id} ارسال شد.\n"
        f"اگر فایل دیگری برای همین سفارش دارید، همینجا بفرستید.",
        reply_markup={
            "inline_keyboard": [[
                {"text": f"پایان ارسال ({count} فایل فرستاده شد)", "callback_data": f"finish_delivery:{order_id}", "icon_custom_emoji_id": PREMIUM_EMOJI["check"][1]}
            ]]
        },
    )
    data["msg_id"] = _msg_id(resp)
    db.set_admin_state(admin_chat_id, "await_config_file", order_id, data)


def _apply_post_delivery_effects(order):
    """بعد از تحویل قطعی یک سفارش پلن (کانفیگ)، تاریخ انقضای اشتراک را برای یادآوری تمدید تنظیم
    و در صورت فعال بودن سیستم رفرال، پورسانت را به دعوت‌کننده اضافه می‌کند.
    توجه: طبق تصمیم مدیریت، این پورسانت فقط برای خرید پلن/کانفیگ اعمال می‌شود، نه خرید یوسی."""
    if order.get("order_type") not in (None, "plan"):
        return
    now = int(time.time())

    plan = db.get_plan(order["plan_id"]) if order.get("plan_id") else None
    if plan and plan.get("category_id"):
        category = db.get_category(plan["category_id"])
        if category and category.get("duration_days"):
            expires_at = now + int(category["duration_days"]) * 86400
            db.set_order_expiry(order["id"], expires_at)

    if not order.get("referral_credited"):
        referrer_id = db.get_referred_by(order["user_id"])
        percent = db.get_referral_percent()
        if referrer_id and percent > 0:
            bonus = int(order["price"] * percent / 100)
            if bonus > 0:
                db.add_referral_balance(referrer_id, bonus, order_id=order["id"])
                db.mark_order_referral_credited(order["id"])
                try:
                    tg.send_message(
                        referrer_id,
                        f"🎁 یکی از دعوت‌شدگان شما خرید کرد و {bonus:,} تومان اعتبار رفرال "
                        f"(فقط قابل‌استفاده در خرید پلن/کانفیگ) به کیف پول شما اضافه شد.",
                    )
                except Exception:
                    logger.exception("خطا در اطلاع‌رسانی پورسانت رفرال")


def _finish_delivery(admin_chat_id, expected_order_id=None, announce=True):
    """سفارش را به‌عنوان تحویل‌شده می‌بندد و {"count": تعداد فایل} را برمی‌گرداند؛
    اگر خودش قبلاً بسته شده باشد False. announce=False یعنی پیام تاییدیه را خود صدازننده (با ویرایش
    همان پیامِ دکمه) نشان می‌دهد."""
    state = db.get_admin_state(admin_chat_id)
    if not state or state.get("action") != "await_config_file":
        return False
    order_id = state["order_id"]
    if expected_order_id is not None and order_id != expected_order_id:
        return False

    order = db.get_order(order_id)
    if not order:
        db.clear_admin_state(admin_chat_id)
        return False

    db.set_order_status(order_id, "delivered")
    db.clear_admin_state(admin_chat_id)
    _apply_post_delivery_effects(order)

    sent_count = (state.get("data") or {}).get("sent_count", 0)
    if announce:
        tg.send_message(admin_chat_id, f"سفارش #{order_id} با {sent_count} فایل تکمیل و بسته شد. ✅")
        _notify_other_admins(order_id, admin_chat_id, "📦 تحویل کامل شد", None, final=True)
    send_rich(
        order["user_id"],
        [("e", "check"), f" همه‌ی فایل‌های سفارش شما برای پلن «{order['plan_name']}» ارسال شد. با تشکر از خرید شما 🙏"],
    )
    return {"count": sent_count}


# ---------------------------------------------------------------------------
# پیام همگانی (Broadcast)
# ---------------------------------------------------------------------------

def _start_broadcast_wizard(chat_id):
    db.set_admin_state(chat_id, "await_broadcast_content", None, {})
    tg.send_message(
        chat_id,
        "📢 پیامی که می‌خواهید ارسال شود را همینجا بفرستید.\n\n"
        "می‌تواند متن، عکس، فایل یا هر نوع پیام دیگری باشد؛ دقیقاً همان چیزی که بفرستید عیناً ارسال می‌شود. "
        "بعد از آن انتخاب می‌کنید برای کدام دسته‌ی مخاطبان فرستاده شود.\n\n"
        "برای انصراف، /cancel را بزنید.",
    )


BROADCAST_SEGMENT_LABELS = {
    "gaming": "خریداران کانفیگ",
    "uc": "خریداران یوسی",
    "never": "استارت‌کرده‌های بدون خرید",
    "all": "همه‌ی کاربران",
}
BROADCAST_SEGMENT_EMOJI = {"gaming": "gaming", "uc": "uc", "never": "pending", "all": "broadcast"}


def broadcast_segment_keyboard():
    rows = []
    for seg in ("gaming", "uc", "never", "all"):
        count = len(db.list_user_ids_by_segment(seg))
        rows.append([btn(f"{BROADCAST_SEGMENT_LABELS[seg]} ({count})", f"bc_seg:{seg}", BROADCAST_SEGMENT_EMOJI[seg])])
    rows.append([btn("انصراف", "broadcast_cancel", "cross")])
    return {"inline_keyboard": rows}


def broadcast_confirm_keyboard(segment="all"):
    return {
        "inline_keyboard": [
            [{"text": "بله، ارسال کن", "callback_data": "broadcast_confirm", "icon_custom_emoji_id": PREMIUM_EMOJI["check"][1]}],
            [btn("تغییر دسته‌ی مخاطبان", "bc_change_seg", "broadcast")],
            [{"text": "انصراف", "callback_data": "broadcast_cancel", "icon_custom_emoji_id": PREMIUM_EMOJI["cross"][1]}],
        ]
    }


def _handle_broadcast_content_received(admin_chat_id, message):
    """پیامی که ادمین به‌عنوان محتوای پیام همگانی فرستاده را ذخیره می‌کند و می‌پرسد برای کدام دسته ارسال شود."""
    message_id = message["message_id"]
    db.set_admin_state(admin_chat_id, "await_broadcast_segment", None, {"message_id": message_id})
    tg.send_message(
        admin_chat_id,
        "پیام بالا را برای کدام دسته ارسال کنم؟ (عدد کنار هر دسته، تعداد مخاطبان است)",
        reply_markup=broadcast_segment_keyboard(),
    )


def _run_broadcast(admin_chat_id, message_id, segment="all"):
    """پیام را با copyMessage برای کاربران دسته‌ی انتخاب‌شده می‌فرستد و گزارش نتیجه را برمی‌گرداند."""
    user_ids = db.list_user_ids_by_segment(segment)
    success, fail = 0, 0
    for uid in user_ids:
        if uid == admin_chat_id:
            continue
        result = tg.copy_message(uid, admin_chat_id, message_id)
        if result and result.get("ok"):
            success += 1
        else:
            fail += 1
    return success, fail


# ---------------------------------------------------------------------------
# مسیر وبهوک
# ---------------------------------------------------------------------------

def _check_renewal_reminders():
    """اشتراک‌هایی که رو به پایان هستند را پیدا کرده، یادآوری با دکمه‌ی تمدید یک‌کلیکی می‌فرستد
    (هر سفارش فقط یک‌بار یادآوری می‌گیرد)."""
    now = int(time.time())
    try:
        due = db.list_orders_due_for_renewal_reminder(now, RENEWAL_REMINDER_WINDOW_SECONDS)
    except Exception:
        logger.exception("خطا در بررسی یادآوری تمدید اشتراک")
        return
    for o in due:
        db.mark_renewal_reminded(o["id"])
        if not o.get("plan_id"):
            continue
        plan = db.get_plan(o["plan_id"])
        if not plan or not plan["active"]:
            continue
        try:
            days_left = max(0, (o["expires_at"] - now) // 86400)
            text, entities = build_message([
                ("e", "duration"), f" اشتراک «{o['plan_name']}» شما ",
                ("b", f"تا {days_left} روز دیگر" if days_left > 0 else "به‌زودی"),
                " به پایان می‌رسد.\n\nبرای جلوگیری از قطعی، همین الان با یک کلیک تمدید کنید:",
            ])
            kb = {"inline_keyboard": [[{"text": "🔄 تمدید سریع همین پلن", "callback_data": f"renew:{plan['id']}"}]]}
            tg.send_message(o["user_id"], text, reply_markup=kb, entities=entities)
        except Exception:
            logger.exception("خطا در ارسال یادآوری تمدید برای سفارش #%s", o["id"])


def _uc_order_is_stale(order):
    """سفارش یوسی که هنوز پرداخت نشده و از مهلت اعتبار قیمت (UC_ORDER_EXPIRY_SECONDS) گذشته است.
    این بررسی مستقل از کار دوره‌ای است تا حتی اگر پاکسازی هنوز اجرا نشده باشد، پرداخت با قیمت کهنه ممکن نشود."""
    if order.get("order_type") != "uc" or order.get("status") != "pending_receipt":
        return False
    return int(time.time()) - int(order.get("created_at") or 0) > UC_ORDER_EXPIRY_SECONDS


def _uc_requote_keyboard(order_id):
    return {"inline_keyboard": [[btn("استعلام مجدد قیمت", f"uc_requote:{order_id}", "refresh")]]}


def _expire_uc_order_and_notify(order, chat_id=None):
    """سفارش یوسی را منقضی می‌کند و پیام «قیمت معتبر نیست» + دکمه‌ی استعلام مجدد می‌فرستد."""
    db.mark_order_expired(order["id"])
    tg.send_message(
        chat_id or order["user_id"],
        f"⏰ سفارش #{order['id']} ({html.escape(order['plan_name'] or '')}) منقضی شد.\n\n"
        f"بیش از {UC_ORDER_EXPIRY_SECONDS // 60} دقیقه گذشته و قیمت لحظه‌ی تتر ممکن است تغییر کرده باشد.\n"
        f"⚠️ این قیمت دیگر معتبر نیست؛ در صورت واریز، رسید تایید نخواهد شد.\n\n"
        f"👇 برای دریافت قیمت جدید و ثبت مجدد همین سفارش، دکمه‌ی زیر را بزنید.",
        reply_markup=_uc_requote_keyboard(order["id"]),
    )


def _check_expired_uc_orders():
    """سفارش‌های یوسی کارت‌به‌کارتی که بیش از UC_ORDER_EXPIRY_SECONDS از ثبتشان گذشته و هنوز
    رسیدی نفرستاده‌اند را منقضی می‌کند و فقط یک‌بار به کاربر اطلاع می‌دهد."""
    cutoff = int(time.time()) - UC_ORDER_EXPIRY_SECONDS
    try:
        stale_orders = db.list_stale_pending_uc_orders(cutoff)
    except Exception:
        logger.exception("خطا در بررسی سفارش‌های منقضی یوسی")
        return
    for o in stale_orders:
        try:
            _expire_uc_order_and_notify(o)
        except Exception:
            logger.exception("خطا در ارسال پیام انقضا برای سفارش #%s", o["id"])


def _cancel_stale_unpaid_orders():
    """سفارش‌های پلن/شارژ کیف پول که بیش از UNPAID_ORDER_EXPIRY_SECONDS بدون رسید مانده‌اند را
    بی‌صدا لغو می‌کند (پیام اضافه برای کاربر نمی‌فرستد). سفارش‌های یوسی جداگانه و با پیام
    مخصوص خودشان در _check_expired_uc_orders منقضی می‌شوند."""
    cutoff = int(time.time()) - UNPAID_ORDER_EXPIRY_SECONDS
    try:
        n = db.cancel_stale_unpaid_orders(cutoff)
        if n:
            logger.info("%s سفارش پرداخت‌نشده‌ی قدیمی لغو شد", n)
    except Exception:
        logger.exception("خطا در لغو سفارش‌های پرداخت‌نشده")


_maintenance_state = {"last_run": 0.0, "last_purge": 0.0}


def _run_maintenance():
    """کارهای دوره‌ای (انقضای سفارش‌ها، پاکسازی، یادآوری تمدید). چون روی هر وبهوک و هر پینگ
    کرون صدا زده می‌شود، حداکثر هر MAINTENANCE_INTERVAL_SECONDS یک‌بار واقعاً اجرا می‌شود."""
    now = time.time()
    if now - _maintenance_state["last_run"] < MAINTENANCE_INTERVAL_SECONDS:
        return
    _maintenance_state["last_run"] = now

    _check_expired_uc_orders()
    _cancel_stale_unpaid_orders()
    _check_renewal_reminders()
    _close_idle_tickets()
    _auto_backup()

    # پاکسازی سفارش‌های لغوشده‌ی بدون رسید: حداکثر هر ۶ ساعت یک‌بار
    if now - _maintenance_state["last_purge"] > 6 * 3600:
        _maintenance_state["last_purge"] = now
        try:
            n = db.purge_cancelled_orders(int(now) - CANCELLED_ORDER_RETENTION_SECONDS)
            if n:
                logger.info("%s سفارش لغوشده‌ی قدیمی از دیتابیس پاک شد", n)
        except Exception:
            logger.exception("خطا در پاکسازی سفارش‌های لغوشده")
        try:
            db.purge_review_cards_older_than(int(now) - 14 * 24 * 3600)
        except Exception:
            logger.exception("خطا در پاکسازی کارت‌های بررسی قدیمی")
        try:
            db.cart_purge_older_than(int(now) - UC_CART_RETENTION_SECONDS)
        except Exception:
            logger.exception("خطا در پاکسازی سبدهای خرید رهاشده")


# ---------------------------------------------------------------------------
# ویرایش کیف پول کاربر توسط ادمین (مشاهده‌ی رسیدها/تراکنش‌ها + افزایش/کاهش موجودی)
# ---------------------------------------------------------------------------

def _parse_amount(text):
    cleaned = (text or "").strip().translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789"))
    cleaned = cleaned.replace(",", "").replace("٬", "").replace(" ", "")
    return int(cleaned) if cleaned.isdigit() else None


def _admin_order_caption(o):
    order_type = o.get("order_type") or "plan"
    lines = [f"🧾 سفارش #{o['id']} — {ORDER_TYPE_LABELS.get(order_type, order_type)}"]
    if order_type != "wallet_topup":
        lines.append(f"محصول: {o.get('plan_name') or '-'}")
        method = "کیف پول" if o.get("payment_method") == "wallet" else "کارت به کارت"
        lines.append(f"روش پرداخت: {method}")
    lines.append(f"مبلغ: {(o.get('price') or 0):,} تومان")
    if o.get("discount_code") and (o.get("discount_amount") or 0) > 0:
        lines.append(f"کد تخفیف: {o['discount_code']} (−{o['discount_amount']:,} تومان)")
    lines.append(f"وضعیت: {ORDER_STATUS_LABELS.get(o['status'], o['status'])}")
    if o.get("created_at"):
        lines.append(f"تاریخ ثبت: {_fmt_ts(o['created_at'])}")
    if o.get("pubg_info"):
        lines.append(f"اطلاعات پابجی: {o['pubg_info']}")
    if order_type == "wallet_topup" and o["status"] == "delivered" and db.has_topup_reversal(o["id"]):
        lines.append("👛 مبلغ این شارژ قبلاً از کیف پول کسر شده است.")
    return "\n".join(lines)


def _send_user_receipts(admin_id, uid, offset=0):
    orders, total = db.list_orders_with_receipt(uid, limit=5, offset=offset)
    if not orders:
        tg.send_message(admin_id, "این کاربر رسیدی ثبت نکرده است." if offset == 0 else "رسید دیگری وجود ندارد.")
        return
    for o in orders:
        kb = None
        cap = _admin_order_caption(o)
        if o.get("receipt_type") == "document":
            tg.send_document_by_file_id(admin_id, o["receipt_file_id"], caption=cap, reply_markup=kb)
        else:
            tg.send_photo_by_file_id(admin_id, o["receipt_file_id"], caption=cap, reply_markup=kb)
    shown = offset + len(orders)
    if shown < total:
        tg.send_message(
            admin_id, f"{shown} از {total} رسید نمایش داده شد.",
            reply_markup={"inline_keyboard": [[btn("رسیدهای قبلی‌تر", f"admin_wrec:{uid}:{shown}", "pending")]]},
        )


def _wallet_tx_button_label(t, total):
    """متن کوتاه دکمه‌ی یک تراکنش (علامت + / − را ایموجی پریمیوم کنار دکمه نشان می‌دهد)."""
    label = WALLET_TX_LABELS.get(t["kind"], t["kind"])
    if t["kind"] == "referral_bonus":
        label = "پورسانت رفرال"
    if t.get("order_id"):
        label += f" #{t['order_id']}"
    return f"{label} · {abs(total):,} تومان"


def _config_display_name(file_name):
    """نام کانفیگ بدون پسوند فایل (i54446.conf ← i54446)."""
    name = (file_name or "-").strip()
    return name.rsplit(".", 1)[0] if "." in name.strip(".") and not name.startswith(".") else name


def _show_admin_wallet_user(admin_id, uid, message_id=None, section="main"):
    """پروفایل یک‌جای کاربر برای ادمین: مشخصات، کیف پول، آخرین سفارش‌ها، تیکت‌ها و دکمه‌ها.
    section="tx" لیست تراکنش‌های کیف پول را نشان می‌دهد."""
    user = db.get_user_row(uid)
    if not user:
        tg.send_message(admin_id, "کاربری با این مشخصات پیدا نشد (باید حداقل یک‌بار ربات را استارت کرده باشد).")
        return False
    balance = user.get("balance") or 0
    ref = user.get("referral_balance") or 0
    if section == "tx":
        txs = db.list_wallet_transactions(uid, limit=10)
        text = f"🧮 تراکنش‌های کیف پول کاربر <code>{uid}</code> (برای جزئیات روی هرکدام بزنید):" if txs else "تراکنشی در کیف پول ثبت نشده است."
        rows = []
        for t in txs:
            total = (t.get("amount") or 0) + (t.get("referral_amount") or 0)
            rows.append([btn(_wallet_tx_button_label(t, total), f"admin_wtx:{uid}:{t['id']}", "plus" if total >= 0 else "minus")])
        rows.append([btn("بازگشت به پروفایل", f"admin_wuser:{uid}", "back")])
        show_screen(admin_id, message_id, text, {"inline_keyboard": rows})
        return True

    head = (
        f"👤 <b>پروفایل کاربر</b>\n"
        f"{html.escape(user.get('full_name') or '-')} (@{html.escape(user.get('username') or '-')})\n"
        f"آیدی: <code>{uid}</code>\n"
    )
    if user.get("first_seen"):
        head += f"عضویت: {_fmt_ts(user['first_seen'])}\n"
    if user.get("blocked"):
        head += "🚫 ربات را بلاک کرده (پیام همگانی به او نمی‌رسد)\n"
    text = head + (
        f"\n💰 موجودی کیف پول: <b>{balance:,}</b> تومان\n"
        f"🎁 اعتبار هدیه/رفرال: {ref:,} تومان | دعوت‌شده‌ها: {db.count_referrals(uid)}\n"
    )
    orders = db.list_orders_for_user(uid, limit=5)
    text += "\n🧾 آخرین سفارش‌ها:\n"
    if orders:
        for o in orders:
            kind = ORDER_TYPE_LABELS.get(o.get("order_type") or "plan", "")
            text += (f"#{o['id']} • {kind} • {html.escape(o.get('plan_name') or '-')} • {(o.get('price') or 0):,} • "
                     f"{ORDER_STATUS_LABELS.get(o['status'], o['status'])}\n")
    else:
        text += "سفارشی ثبت نشده.\n"
    files = db.list_delivered_files(uid, limit=3)
    if files:
        text += "\n📁 آخرین کانفیگ‌های فرستاده‌شده:\n"
        for f in files:
            text += f"<code>{html.escape(_config_display_name(f['file_name']))}</code>\n"  # با یک لمس کپی می‌شود
    tickets = db.list_tickets_for_user(uid, limit=3)
    text += "\n🎫 تیکت‌ها:\n"
    open_ticket = None
    if tickets:
        for t in tickets:
            msgs = db.list_ticket_messages(t["id"], limit=3)
            last = html.escape((msgs[-1]["text"] or "")[:50]) if msgs else "-"
            state_fa = "باز" if t["status"] == "open" else "بسته"
            text += f"#{t['id']} ({state_fa}) — {last}\n"
            if t["status"] == "open" and not open_ticket:
                open_ticket = t
    else:
        text += "تیکتی ندارد.\n"

    rows = [
        [btn("پیام به کاربر", f"msguser:{uid}", "contact"), btn("ویرایش موجودی", f"wallet_adj:set:{uid}", "wallet")],
        [btn("تراکنش‌های کیف پول", f"admin_wtxs:{uid}", "history"), btn("رسیدها", f"admin_wrec:{uid}:0", "pending")],
    ]
    if open_ticket:
        rows.append([btn(f"پاسخ به تیکت #{open_ticket['id']}", f"ticket_reply_start:{open_ticket['id']}", "reply"),
                     btn("بستن تیکت", f"ticket_close:{open_ticket['id']}", "lock")])
    rows.append([btn("پنل مدیریت", "admin_menu", "admin")])
    show_screen(admin_id, message_id, text, {"inline_keyboard": rows})
    return True


def _show_admin_wallet_tx(admin_id, uid, tx_id, message_id=None):
    t = db.get_wallet_transaction(tx_id, uid)
    if not t:
        tg.send_message(admin_id, "این تراکنش پیدا نشد.")
        return
    total = (t.get("amount") or 0) + (t.get("referral_amount") or 0)
    label = WALLET_TX_LABELS.get(t["kind"], t["kind"])
    parts = [("e", "plus" if total >= 0 else "minus"), " ", ("b", f"جزئیات تراکنش: {label}"), "\n\n",
             f"کاربر: {uid}\n"]
    if t.get("order_id"):
        parts.append(f"سفارش: #{t['order_id']}\n")
    parts.append(f"مبلغ: {'+' if total >= 0 else '−'}{abs(total):,} تومان\n")
    if t.get("referral_amount"):
        parts.append(f"(سهم اعتبار رفرال: {t['referral_amount']:+,} تومان)\n")
    parts.append(f"موجودی بعد از تراکنش: {(t.get('balance_after') or 0):,} تومان\n")
    if t.get("created_at"):
        parts.append(f"تاریخ: {_fmt_ts(t['created_at'])}\n")
    if t.get("note"):
        parts.append(f"📝 توضیح: {t['note']}\n")
    order = db.get_order(t["order_id"]) if t.get("order_id") else None
    if order and order.get("receipt_file_id"):
        # رسید همین تراکنش (خرید/شارژ) همراه جزئیاتش ارسال می‌شود؛ پیام عکس قابل ویرایش نیست، پس بازگشت پیام جدید می‌فرستد
        cap = "".join(p if isinstance(p, str) else (PREMIUM_EMOJI[p[1]][0] if p[0] == "e" else p[1]) for p in parts)
        cap = cap + "🧾 رسید پرداخت این تراکنش در همین پیام است."
        kb = {"inline_keyboard": [[btn("بازگشت", f"admin_wuser:{uid}:n", "back")]]}
        if order.get("receipt_type") == "document":
            tg.send_document_by_file_id(admin_id, order["receipt_file_id"], caption=cap, reply_markup=kb)
        else:
            tg.send_photo_by_file_id(admin_id, order["receipt_file_id"], caption=cap, reply_markup=kb)
        return
    text, entities = build_message(parts)
    kb = {"inline_keyboard": [[btn("بازگشت", f"admin_wuser:{uid}", "back")]]}
    if message_id:
        tg.edit_message_text(admin_id, message_id, text, reply_markup=kb, entities=entities)
    else:
        tg.send_message(admin_id, text, reply_markup=kb, entities=entities)


def _wallet_confirm_keyboard():
    return {"inline_keyboard": [
        [btn("اعمال و اطلاع به کاربر", "wallet_apply:1", "check")],
        [btn("اعمال بدون اطلاع به کاربر", "wallet_apply:0", "check")],
        [btn("انصراف", "wallet_cancel", "cross")],
    ]}


def _ask_wallet_confirm(admin_id, data):
    uid, amount, mode = data["uid"], data["amount"], data["mode"]
    old = db.get_user_balance(uid)
    if mode == "set":
        new = amount
    else:
        new = old + amount if mode == "inc" else old - amount
    delta = new - old
    if delta == 0:
        db.clear_admin_state(admin_id)
        tg.send_message(admin_id, f"موجودی فعلی کاربر همین الان {old:,} تومان است؛ تغییری لازم نیست.")
        return
    db.set_admin_state(admin_id, "await_wallet_confirm", None, data)
    tg.send_message(
        admin_id,
        f"⚠️ تایید نهایی\n\n"
        f"کاربر: <code>{uid}</code>\n"
        f"موجودی: {old:,} ← <b>{new:,}</b> تومان "
        f"({'افزایش' if delta > 0 else 'کاهش'} {abs(delta):,} تومان)\n"
        f"دلیل: {html.escape(data.get('note') or '-')}",
        reply_markup=_wallet_confirm_keyboard(),
    )


def _handle_admin_wallet_text(chat_id, text, state):
    action = state["action"]
    data = state.get("data") or {}
    if action == "await_msguser_lookup":
        raw = text.strip().lstrip("@").translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789"))
        if raw.isdigit():
            uid = int(raw)
        else:
            found = db.find_user_by_username(raw)
            uid = found["user_id"] if found else None
        if uid is None or not db.get_user_row(uid):
            tg.send_message(chat_id, "کاربر پیدا نشد. آیدی عددی (یا یوزرنیم) را دوباره بفرستید یا /cancel بزنید.")
            return
        db.set_admin_state(chat_id, "await_support_msg", None, {"uid": uid})
        tg.send_message(
            chat_id,
            f"✍️ پیامی را که می‌خواهید از طرف پشتیبانی برای کاربر <code>{uid}</code> بفرستید، همین‌جا بنویسید.\n"
            f"پیام با عنوان «پیام از پشتیبانی» ارسال می‌شود و کاربر با دکمه‌ی «پاسخ به پشتیبانی» می‌تواند جواب بدهد.\n\n"
            f"(برای انصراف، /cancel)",
        )
        return
    if action == "await_wallet_user":
        raw = text.strip().translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789"))
        if raw.isdigit():
            uid = int(raw)
        else:
            found = db.find_user_by_username(raw)
            uid = found["user_id"] if found else None
        if uid is None or not db.get_user_row(uid):
            tg.send_message(chat_id, "کاربر پیدا نشد. آیدی عددی (یا یوزرنیم) را دوباره بفرستید یا /cancel بزنید.")
            return
        db.clear_admin_state(chat_id)
        _show_admin_wallet_user(chat_id, uid)
        return
    if action == "await_wallet_amount":
        amount = _parse_amount(text)
        if data.get("mode") == "set":
            if amount is None:
                tg.send_message(chat_id, "موجودی را فقط با عدد و به تومان بفرستید، مثلاً: 500000 (برای صفر، 0) یا /cancel.")
                return
            data["amount"] = amount
            db.set_admin_state(chat_id, "await_wallet_note", None, data)
            tg.send_message(chat_id, "دلیل این تغییر را بنویسید (مثلاً: واریز اشتباه تایید شد). این متن در تاریخچه ثبت می‌شود.\n\n(/cancel برای انصراف)")
            return
        if not amount or amount <= 0:
            tg.send_message(chat_id, "مبلغ را فقط با عدد و به تومان بفرستید، مثلاً: 500000 (یا /cancel).")
            return
        if data.get("mode") == "dec":
            bal = db.get_user_balance(data["uid"])
            if amount > bal:
                tg.send_message(chat_id, f"موجودی کاربر {bal:,} تومان است؛ بیشتر از آن نمی‌شود کم کرد. مبلغ کمتری بفرستید (یا /cancel).")
                return
        data["amount"] = amount
        db.set_admin_state(chat_id, "await_wallet_note", None, data)
        tg.send_message(chat_id, "دلیل این تغییر را بنویسید (مثلاً: واریز اشتباه تایید شد). این متن در تاریخچه ثبت می‌شود.\n\n(/cancel برای انصراف)")
        return
    if action == "await_wallet_note":
        note = text.strip()
        if not note or len(note) > 200:
            tg.send_message(chat_id, "دلیل را در حداکثر ۲۰۰ کاراکتر بنویسید (یا /cancel).")
            return
        data["note"] = note
        _ask_wallet_confirm(chat_id, data)
        return


def _handle_admin_wallet_callback(cq, data):
    cq_id = cq["id"]
    admin_id = cq["from"]["id"]
    if data == "admin_wallet_start":
        db.set_admin_state(admin_id, "await_wallet_user", None, {})
        tg.answer_callback_query(cq_id)
        tg.send_message(admin_id, "👤 آیدی عددی کاربر (یا یوزرنیم) را بفرستید.\n\n(/cancel برای انصراف)")
        return
    if data == "admin_msguser_start":
        db.set_admin_state(admin_id, "await_msguser_lookup", None, {})
        tg.answer_callback_query(cq_id)
        tg.send_message(admin_id, "👤 آیدی عددی کاربر (یا یوزرنیم) را بفرستید.\n\n(/cancel برای انصراف)")
        return
    if data == "wallet_cancel":
        db.clear_admin_state(admin_id)
        tg.answer_callback_query(cq_id, "لغو شد.")
        show_screen(admin_id, cq.get("message", {}).get("message_id"), "عملیات لغو شد.")
        return
    if data.startswith("admin_wtx:"):
        try:
            _, uid, tx_id = data.split(":")
            uid, tx_id = int(uid), int(tx_id)
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        tg.answer_callback_query(cq_id)
        _show_admin_wallet_tx(admin_id, uid, tx_id, cq.get("message", {}).get("message_id"))
        return
    if data.startswith("admin_wtxs:"):
        try:
            uid = int(data.split(":")[1])
        except (ValueError, IndexError):
            tg.answer_callback_query(cq_id)
            return
        tg.answer_callback_query(cq_id)
        _show_admin_wallet_user(admin_id, uid, message_id=cq.get("message", {}).get("message_id"), section="tx")
        return
    if data.startswith("admin_wuser:"):
        bits = data.split(":")
        try:
            uid = int(bits[1])
        except (ValueError, IndexError):
            tg.answer_callback_query(cq_id)
            return
        tg.answer_callback_query(cq_id)
        as_new = len(bits) > 2 and bits[2] == "n"
        _show_admin_wallet_user(admin_id, uid, message_id=None if as_new else cq.get("message", {}).get("message_id"))
        return
    if data.startswith("admin_wrec:"):
        _, uid, off = data.split(":")
        tg.answer_callback_query(cq_id)
        _send_user_receipts(admin_id, int(uid), int(off))
        return
    if data.startswith("wallet_adj:"):
        _, mode, uid = data.split(":")
        if mode not in ("inc", "dec", "set"):
            tg.answer_callback_query(cq_id)
            return
        uid = int(uid)
        tg.answer_callback_query(cq_id)
        db.set_admin_state(admin_id, "await_wallet_amount", None, {"uid": uid, "mode": "set" if mode == "set" else mode})
        bal = db.get_user_balance(uid)
        if mode == "set":
            tg.send_message(
                admin_id,
                f"💰 موجودی فعلی کاربر: {bal:,} تومان\n\n"
                f"موجودیِ صحیح کیف پول باید چند تومان باشد؟ عدد را فقط با ارقام بفرستید؛ همان مقدار مستقیم ثبت می‌شود.\n"
                f"(برای صفر کردن، 0 بفرستید)\n\n(/cancel برای انصراف)",
            )
        else:
            tg.send_message(
                admin_id,
                f"مبلغ {'افزایش' if mode == 'inc' else 'کاهش'} را به تومان و فقط با عدد بفرستید.\n"
                f"موجودی فعلی کاربر: {bal:,} تومان\n\n(/cancel برای انصراف)",
            )
        return
    if data.startswith("wallet_revtop:"):
        order = db.get_order(int(data.split(":", 1)[1]))
        if not order or order.get("order_type") != "wallet_topup" or order["status"] != "delivered":
            tg.answer_callback_query(cq_id, "این سفارش شارژِ تاییدشده نیست.", show_alert=True)
            return
        if db.has_topup_reversal(order["id"]):
            tg.answer_callback_query(cq_id, "مبلغ این شارژ قبلاً کسر شده است.", show_alert=True)
            return
        bal = db.get_user_balance(order["user_id"])
        if order["price"] > bal:
            tg.answer_callback_query(
                cq_id, f"موجودی کاربر {bal:,} تومان است و کمتر از مبلغ شارژ ({order['price']:,}) است؛ از «کاهش موجودی» مبلغ دلخواه بزنید.",
                show_alert=True,
            )
            return
        tg.answer_callback_query(cq_id)
        _ask_wallet_confirm(admin_id, {
            "uid": order["user_id"], "mode": "dec", "amount": order["price"], "order_id": order["id"],
            "note": f"کسر شارژ اشتباهِ سفارش #{order['id']}",
        })
        return
    if data in ("wallet_apply:0", "wallet_apply:1"):
        state = db.get_admin_state(admin_id)
        if not state or state.get("action") != "await_wallet_confirm":
            tg.answer_callback_query(cq_id, "این درخواست منقضی شده یا قبلاً انجام شده.", show_alert=True)
            return
        d = state["data"]
        db.clear_admin_state(admin_id)   # جلوگیری از اعمال دوباره با کلیک تکراری
        note = f"ادمین {admin_id}: {d.get('note') or ''}".strip()
        if d["mode"] == "set":
            # موجودی دقیقاً روی مقدار واردشده تنظیم می‌شود؛ اختلاف با موجودیِ لحظه‌ی اعمال محاسبه می‌شود
            cur = db.get_user_balance(d["uid"])
            delta = d["amount"] - cur
            note += f" (تنظیم موجودی روی {d['amount']:,})"
            if delta == 0:
                tg.answer_callback_query(cq_id, "موجودی همین حالا برابر مقدار واردشده است.", show_alert=True)
                return
        else:
            delta = d["amount"] if d["mode"] == "inc" else -d["amount"]
        new_bal = db.admin_adjust_balance(d["uid"], delta, note=note, order_id=d.get("order_id"))
        if new_bal is None:
            tg.answer_callback_query(cq_id, "موجودی کاربر برای این کسر کافی نیست.", show_alert=True)
            return
        tg.answer_callback_query(cq_id, "انجام شد.")
        verb = "افزایش" if delta > 0 else "کاهش"
        d["amount"] = abs(delta)
        removed_bonus = db.reverse_topup_bonus(d["order_id"]) if (delta < 0 and d.get("order_id")) else 0
        bonus_note = f"\n🎁 هدیه‌ی همان شارژ هم کسر شد: {removed_bonus:,} تومان" if removed_bonus else ""
        show_screen(admin_id, cq.get("message", {}).get("message_id"),
                    f"✅ موجودی کاربر <code>{d['uid']}</code> {abs(delta):,} تومان {verb} یافت.\nموجودی جدید: <b>{new_bal:,}</b> تومان{bonus_note}")
        for other in ADMIN_IDS:
            if other != admin_id:
                try:
                    tg.send_message(
                        other,
                        f"🧮 ادمین {admin_id} موجودی کاربر <code>{d['uid']}</code> را {d['amount']:,} تومان {verb} داد "
                        f"(موجودی جدید: {new_bal:,}).\nدلیل: {html.escape(d.get('note') or '-')}",
                    )
                except Exception:
                    logger.exception("اطلاع تعدیل کیف پول به ادمین %s ناموفق بود", other)
        if data.endswith(":1"):
            try:
                tg.send_message(
                    d["uid"],
                    f"🧮 موجودی کیف پول شما توسط پشتیبانی {d['amount']:,} تومان {verb} یافت.\n"
                    f"دلیل: {html.escape(d.get('note') or '-')}\nموجودی فعلی: {new_bal:,} تومان",
                )
            except Exception:
                tg.send_message(admin_id, "⚠️ پیام اطلاع‌رسانی به کاربر ارسال نشد (احتمالاً ربات را بلاک کرده).")
        return


# ---------------------------------------------------------------------------
# بکاپ خودکار دیتابیس برای ادمین اصلی + هشدار خطا
# ---------------------------------------------------------------------------

BACKUP_INTERVAL_SECONDS = 24 * 3600


def _make_db_backup():
    """یک نسخه‌ی سازگار از دیتابیس (با sqlite backup API) می‌گیرد و فشرده می‌کند؛ مسیر فایل .gz را برمی‌گرداند."""
    stamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M")
    raw = os.path.join(tempfile.gettempdir(), f"wg_bot_backup_{stamp}.db")
    gz_path = raw + ".gz"
    src = sqlite3.connect(config.DB_PATH, timeout=30)
    dst = sqlite3.connect(raw)
    try:
        src.backup(dst)
    finally:
        dst.close()
        src.close()
    try:
        with open(raw, "rb") as f_in, gzip.open(gz_path, "wb") as f_out:
            while True:
                chunk = f_in.read(1024 * 1024)
                if not chunk:
                    break
                f_out.write(chunk)
    finally:
        try:
            os.remove(raw)
        except OSError:
            pass
    return gz_path


def _send_backup(chat_id, manual=False):
    """بکاپ را می‌سازد و برای chat_id می‌فرستد. True/False برمی‌گرداند."""
    path = None
    try:
        path = _make_db_backup()
        size_kb = os.path.getsize(path) // 1024
        caption = (
            f"💾 بکاپ {'دستی' if manual else 'خودکار'} دیتابیس — "
            f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M')} ({size_kb} KB)"
        )
        res = tg.send_local_document(chat_id, path, caption=caption)
        ok = bool(res.get("ok"))
        if not ok:
            logger.error("ارسال بکاپ ناموفق بود: %s", res)
        return ok
    except Exception:
        logger.exception("خطا در ساخت/ارسال بکاپ")
        _alert_admin_error("بکاپ دیتابیس")
        return False
    finally:
        if path:
            try:
                os.remove(path)
            except OSError:
                pass


def _auto_backup():
    """هر ۲۴ ساعت یک‌بار (از داخل _run_maintenance) بکاپ دیتابیس را برای ادمین اصلی می‌فرستد."""
    now = int(time.time())
    try:
        last = int(db.get_setting("last_backup_ts", "0") or 0)
    except ValueError:
        last = 0
    if now - last < BACKUP_INTERVAL_SECONDS:
        return
    # قبل از شروع زمان را ثبت می‌کنیم تا دو وبهوک هم‌زمان دو بکاپ نسازند
    db.set_setting("last_backup_ts", str(now))
    if not _send_backup(config.ADMIN_CHAT_ID):
        # اگر ناموفق بود، ۱ ساعت بعد دوباره تلاش شود
        db.set_setting("last_backup_ts", str(now - BACKUP_INTERVAL_SECONDS + 3600))


_error_alert_state = {}
ERROR_ALERT_COOLDOWN_SECONDS = 600


def _alert_admin_error(context, update=None):
    """خطای در حال رخ‌دادن را (کوتاه) برای ادمین اصلی می‌فرستد. خطای تکراری تا ۱۰ دقیقه دوباره ارسال نمی‌شود."""
    try:
        tb_lines = traceback.format_exc().strip().splitlines()
        last_line = tb_lines[-1] if tb_lines else "نامشخص"
        sig = f"{context}|{last_line[:120]}"
        now = time.time()
        if now - _error_alert_state.get(sig, 0) < ERROR_ALERT_COOLDOWN_SECONDS:
            return
        if len(_error_alert_state) > 200:
            _error_alert_state.clear()
        _error_alert_state[sig] = now

        where = ""
        if isinstance(update, dict):
            if "callback_query" in update:
                cq = update["callback_query"]
                where = f"\nکاربر: {cq.get('from', {}).get('id')} | دکمه: {cq.get('data', '')[:40]}"
            elif "message" in update:
                m = update["message"]
                kind = "عکس" if "photo" in m else ("فایل" if "document" in m else "متن")
                where = f"\nکاربر: {m.get('from', {}).get('id')} | نوع پیام: {kind}"
        short_tb = "\n".join(tb_lines[-4:])[:700]
        text = (
            f"⚠ خطا در ربات\nبخش: {html.escape(context)}{html.escape(where)}\n\n"
            f"<pre>{html.escape(short_tb)}</pre>"
        )
        tg.send_message(config.ADMIN_CHAT_ID, text)
    except Exception:
        logger.exception("ارسال هشدار خطا به ادمین ناموفق بود")


def _close_idle_tickets():
    """تیکت‌هایی که پشتیبانی آخرین پاسخ را داده و کاربر TICKET_AUTO_CLOSE_SECONDS جواب نداده را می‌بندد.
    تیکتی که منتظر پاسخ پشتیبانی است هیچ‌وقت خودکار بسته نمی‌شود."""
    cutoff = int(time.time()) - TICKET_AUTO_CLOSE_SECONDS
    for t in db.list_idle_tickets_awaiting_user(cutoff):
        try:
            db.close_ticket(t["id"])
            st = db.get_admin_state(t["user_id"])
            if st and st.get("action") == "await_ticket_message" and (st.get("data") or {}).get("ticket_id") == t["id"]:
                db.clear_admin_state(t["user_id"])
            tg.send_message(
                t["user_id"],
                f"🔒 تیکت #{t['id']} به‌دلیل {_ticket_auto_close_label()} بی‌پاسخ ماندن، خودکار بسته شد.\n"
                "اگر هنوز مشکلی دارید، از «ارتباط با ما» یک تیکت جدید بزنید.",
            )
        except Exception:
            logger.exception("خطا در بستن خودکار تیکت %s", t.get("id"))


def _webhook_secret_ok():
    """هدر امضای تلگرام (secret_token) را با مقدار config مقایسه می‌کند (مقایسه‌ی ایمن در برابر timing).
    اگر WEBHOOK_SECRET خالی باشد، بررسی غیرفعال است."""
    secret = getattr(config, "WEBHOOK_SECRET", "") or ""
    if not secret:
        return True
    got = request.headers.get("X-Telegram-Bot-Api-Secret-Token", "")
    return hmac.compare_digest(got.encode("utf-8"), secret.encode("utf-8"))


def _setup_key_ok():
    """آدرس‌های راه‌اندازی (/set_webhook و /set_commands) فقط با ?key=SETUP_KEY کار می‌کنند.
    اگر SETUP_KEY خالی باشد این آدرس‌ها کاملاً بسته می‌شوند."""
    expected = getattr(config, "SETUP_KEY", "") or ""
    if not expected:
        return False
    got = request.args.get("key", "")
    return hmac.compare_digest(got.encode("utf-8"), expected.encode("utf-8"))


@app.route(config.WEBHOOK_PATH, methods=["POST"])
def webhook():
    if not _webhook_secret_ok():
        logger.warning("درخواست وبهوک با secret_token نادرست رد شد")
        return jsonify({"ok": False, "error": "forbidden"}), 403

    update = request.get_json(force=True, silent=True) or {}
    logger.info("Update received: %s", update.get("update_id"))

    try:
        _run_maintenance()
    except Exception:
        logger.exception("خطا در کارهای دوره‌ای (webhook)")
        _alert_admin_error("کارهای دوره‌ای")

    try:
        if "message" in update:
            message = update["message"]
            from_user = message.get("from", {})
            chat_id = message["chat"]["id"]
            user_id = from_user.get("id", chat_id)

            # -------- ثبت/به‌روزرسانی کاربر برای پیام همگانی (برای همه، حتی قبل از چک عضویت) --------
            if not from_user.get("is_bot"):
                full_name = (from_user.get("first_name", "") + " " + from_user.get("last_name", "")).strip()
                db.upsert_user(user_id, from_user.get("username"), full_name)

            # -------- اگر ادمین وسط ویزارد «پیام همگانی» است، این پیام محتوای برادکست است --------
            # (این چک قبل از هر چیز دیگری انجام می‌شود چون باید هر نوع پیامی -متن/عکس/فایل- را بپذیرد)
            if is_admin(chat_id):
                admin_state = db.get_admin_state(chat_id)
                if admin_state and admin_state.get("action") == "await_broadcast_content":
                    _handle_broadcast_content_received(chat_id, message)
                    return jsonify({"ok": True})

            # -------- چک عضویت در کانال، قبل از هر پردازشی (هر مرحله‌ای که کاربر باشد) --------
            if not is_admin(chat_id) and not is_member_of_channel(user_id):
                send_force_join_message(chat_id)
                return jsonify({"ok": True})

            if "photo" in message:
                handle_photo(message)
            elif "document" in message:
                handle_document(message)
            elif "video" in message or "audio" in message or "voice" in message:
                handle_other_media(message)
            elif "text" in message:
                handle_text_message(message)

        elif "callback_query" in update:
            cq = update["callback_query"]
            from_chat_id = cq["from"]["id"]
            data = cq.get("data", "")
            from_user = cq.get("from", {})

            if not from_user.get("is_bot"):
                full_name = (from_user.get("first_name", "") + " " + from_user.get("last_name", "")).strip()
                db.upsert_user(from_chat_id, from_user.get("username"), full_name)

            # دکمه‌ی «بررسی عضویت» خودش باید همیشه قابل کلیک باشد تا نتیجه‌ی تازه را چک کند
            if data != "check_membership" and not is_admin(from_chat_id) and not is_member_of_channel(from_chat_id):
                tg.answer_callback_query(cq["id"], "❌ شما عضو کانال نیستید!", show_alert=True)
                send_force_join_message(from_chat_id)
                return jsonify({"ok": True})

            handle_callback_query(cq)
    except Exception:
        logger.exception("Error while handling update")
        _alert_admin_error("پردازش پیام کاربر", update)

    return jsonify({"ok": True})


@app.route("/set_webhook", methods=["GET"])
def route_set_webhook():
    """
    یک بار این آدرس را در مرورگر باز کنید تا وبهوک ثبت شود:
    https://yourdomain.com/set_webhook?key=<SETUP_KEY>
    (کلید SETUP_KEY در config.py است؛ بدون آن خطای 403 می‌دهد.)
    """
    if not _setup_key_ok():
        return jsonify({"ok": False, "error": "forbidden"}), 403
    full_url = config.BASE_URL.rstrip("/") + config.WEBHOOK_PATH
    result = tg.set_webhook(full_url, secret_token=getattr(config, "WEBHOOK_SECRET", "") or None)
    return jsonify(result)


@app.route("/set_commands", methods=["GET"])
def route_set_commands():
    """
    یک بار این آدرس را در مرورگر باز کنید تا منوی دستورات (دکمه‌ی /) تنظیم شود:
    https://yourdomain.com/set_commands?key=<SETUP_KEY>
    """
    if not _setup_key_ok():
        return jsonify({"ok": False, "error": "forbidden"}), 403
    default_commands = [
        {"command": "start", "description": "شروع و مشاهده پلن‌ها"},
        {"command": "plans", "description": "لیست پلن‌های موجود"},
    ]
    admin_commands = default_commands + [
        {"command": "admin", "description": "پنل مدیریت (افزودن/حذف پلن، سفارش‌ها)"},
        {"command": "pending", "description": "سفارش‌های در انتظار بررسی یا ارسال فایل"},
        {"command": "report", "description": "گزارش فروش"},
        {"command": "tickets", "description": "تیکت‌های پشتیبانی باز"},
        {"command": "ledger", "description": "دفتر تراکنش کیف پول یک کاربر (/ledger آیدی)"},
        {"command": "broadcast", "description": "ارسال پیام همگانی (انتخاب دسته‌ی مخاطبان)"},
        {"command": "done", "description": "بستن دستی سفارش جاری"},
        {"command": "cancel", "description": "لغو عملیات نیمه‌کاره"},
    ]
    result_default = tg.set_my_commands(default_commands)
    result_admin = {}
    for admin_id in ADMIN_IDS:
        result_admin[str(admin_id)] = tg.set_my_commands(admin_commands, chat_id=admin_id)
    return jsonify({"default": result_default, "admin": result_admin})


@app.route("/", methods=["GET"])
def index():
    try:
        _run_maintenance()
    except Exception:
        logger.exception("خطا در کارهای دوره‌ای (index)")
    return "WireGuard Sales Bot is running."


if __name__ == "__main__":
    # فقط برای تست لوکال - روی هاست از Passenger/WSGI استفاده می‌شود (passenger_wsgi.py)
    app.run(host="0.0.0.0", port=5000, debug=True)
