# -*- coding: utf-8 -*-
"""
ربات فروش پرمیوم تلگرام و استارز.
معماری و الگوهای امنیتی (وبهوک با secret_token، رسید تکراری، تصمیم اتمیک ادمین،
ایموجی‌های پریمیوم روی دکمه‌ها) عیناً از همان ربات وایرگارد گرفته شده.
"""

import hmac
import json
import logging
import math
import datetime
import re
import sqlite3
import time

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("premium_stars_bot")

app = Flask(__name__)
db.init_db()
db.init_extra()

# ---------------------------------------------------------------------------
# ایموجی‌های پریمیوم — کدهایی که خودتان فرستادید + چندتای دیگر که مشابه ربات وایرگارد
# روی همین اکانت تست‌شده‌اند (چون هر دو ربات احتمالاً زیر یک اکانت صاحب‌ربات پریمیوم هستند).
# اگر آیکون‌ها روی دکمه نمایش داده نشدند، PREMIUM_BUTTON_ICONS را در config.py به False بدل کنید.
# ---------------------------------------------------------------------------
PREMIUM_EMOJI = {
    "premium":  ("💎", "5339520934573255920"),  # پرمیوم تلگرام (کد خودتان)
    "stars":    ("⭐", "5438496463044752972"),   # استارز (کد خودتان)
    "card":     ("💳", "5445353829304387411"),
    "check":    ("✅", "5469891741970605890"),
    "cross":    ("❌", "5469806645783571411"),
    "back":     ("🔙", "5462931610028510371"),
    "home":     ("🏠", "5465226866321268133"),
    "admin":    ("🛠", "5341715473882955310"),
    "pending":  ("🧾", "5810003553531467794"),
    "refresh":  ("🔄", "5433878454078556670"),
    "warning":  ("⚠", "5469940983770657399"),
    "usdt":     ("💵", "5470060860602856810"),
    "edit":     ("✏", "5395444784611480792"),
    "clock":    ("⏰", "6030821505984630931"),
    "broadcast": ("📢", "5472037524811555705"),
    "users":    ("👥", "5958460691550572213"),
    "stats":    ("📊", "5244837092042750681"),
    "ticket":   ("🎫", "5958416857114350105"),
    "lock":     ("🔒", "5296369303661067030"),
    "trash":    ("🗑", "5445267414562389170"),
    "plus":     ("➕", "5393194986252542669"),
    "contact":  ("📞", "5465169893580086142"),
    # --- هم‌نام و هم‌کد با ربات وایرگارد ---
    "wallet":   ("👛", "5242296412728741790"),  # کیف پول
    "shop":     ("🛍", "5312361253610475399"),  # فروشگاه
    "invite":   ("🎁", "5199749070830197566"),  # دعوت دوستان
    "pricelist": ("📋", "5962854524698760122"),  # تاریخچه تراکنش‌ها
    # --- تیکت/پشتیبانی و احراز هویت (همان آیدی‌های ربات وایرگارد) ---
    "msg":      ("💬", "5472239203590888751"),  # پیام داخل تیکت‌ها
    "reply":    ("↩", "5834924817798665219"),   # پاسخ
    "info":     ("ℹ", "5445331903496332384"),   # اطلاع‌رسانی
    "num1":     ("1️⃣", "5461033346152804686"),  # عدد یک (احراز هویت)
    "num2":     ("2️⃣", "5469622950032321924"),  # عدد دو (احراز هویت)
    "forbid":   ("⛔", "5915838346638138046"),   # علامت ممنوع (احراز هویت)
    "bell":     ("🔔", "5472405023688237378"),   # دکمه‌ی «وقتی فروش شروع شد خبرم کن»
}

# ایموجی‌های بخش احراز هویت عمداً در نگاشت خودکار نیستند (فقط با ("e", "num1") و ... دستی استفاده می‌شوند)
_NO_AUTO_MAP = {"num1", "num2", "forbid", "bell"}
tg.set_premium_map({
    PREMIUM_EMOJI[k][0]: PREMIUM_EMOJI[k][1]
    for k in PREMIUM_EMOJI if k not in _NO_AUTO_MAP
})


def _utf16_len(s):
    return len(s.encode("utf-16-le")) // 2


def build_message(parts):
    """پیام ترکیبی (متن + بولد + کد + ایموجی پریمیوم) می‌سازد -> (text, 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 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)


def btn(text, callback_data=None, emoji=None, url=None, style=None):
    """style: None | "primary" (آبی) | "success" (سبز) | "danger" (قرمز) — طبق آپدیت جدید Bot API تلگرام."""
    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 and getattr(config, "BUTTON_STYLES_ENABLED", True):
        button["style"] = style
    return button


def is_admin(user_id):
    return user_id in config.ADMIN_IDS


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, parse_mode=None)


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


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


KYC_MIN_PHOTOS = getattr(config, "KYC_MIN_PHOTOS", 2)

_TEHRAN_TZ = datetime.timezone(datetime.timedelta(hours=3, minutes=30))


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


def normalize_digits(text):
    return text.translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789"))


# ---------------------------------------------------------------------------
# نرخ لحظه‌ای تتر (همان صرافی Wallex که در ربات قبلی استفاده شده)
# ---------------------------------------------------------------------------

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


def compute_price(base_usd, usdt_rate, multiplier):
    raw = base_usd * usdt_rate * multiplier
    return int(math.ceil(raw / 1000) * 1000)


# ---------------------------------------------------------------------------
# یوزرنیم ربات (برای لینک رفرال)
# ---------------------------------------------------------------------------

_bot_username_cache = {"value": (getattr(config, "BOT_USERNAME", "") or "").lstrip("@") or None}


def get_bot_username():
    if _bot_username_cache["value"]:
        return _bot_username_cache["value"]
    try:
        me = tg.get_me()
        username = (me.get("result") or {}).get("username")
        if username:
            _bot_username_cache["value"] = username
            return username
    except Exception:
        logger.exception("خطا در getMe")
    return None


# ---------------------------------------------------------------------------
# عضویت اجباری در کانال
# ---------------------------------------------------------------------------

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


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


def is_member_of_channel(user_id):
    """True اگر کاربر عضو/ادمین/سازنده‌ی کانال اجباری باشد. ربات باید در کانال ادمین باشد."""
    try:
        result = tg._post("getChatMember", chat_id=config.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
    return (result.get("result") or {}).get("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 nav_row(back_callback):
    """ردیف مشترک «بازگشت» (یک مرحله بالاتر) و «منوی اصلی» — در همه‌ی بخش‌ها."""
    return [
        btn("بازگشت", back_callback, "back"),
        btn("منوی اصلی", "back:main", "home"),
    ]


def main_menu_keyboard(user_id):
    rows = []
    if getattr(config, "MINIAPP_ENABLED", True) and config.BASE_URL.startswith("https://"):
        rows.append([_mini_button()])
    rows += [
        [btn("فروشگاه", "menu:shop", "shop", style="success")],
        [btn("کیف پول", "menu:wallet", "wallet"), btn("سفارش‌های من", "menu:myorders", "pending")],
        [btn("دعوت دوستان", "menu:referral", "invite"), btn("ارتباط با ما", "menu:contact", "contact")],
    ]
    if is_admin(user_id):
        rows.append([btn("پنل مدیریت", "menu:admin", "admin")])
    return {"inline_keyboard": rows}


def shop_menu_keyboard():
    return {"inline_keyboard": [
        [btn("پرمیوم تلگرام" + (" ⏸" if db.get_pause("premium") else ""), "menu:premium", "premium")],
        [btn("استارز تلگرام" + (" ⏸" if db.get_pause("stars") else ""), "menu:stars", "stars")],
        nav_row("back:main"),
    ]}


def premium_menu_keyboard(usdt_rate):
    multiplier = db.get_premium_multiplier()
    rows = []
    for p in db.list_packages("premium"):
        price = compute_price(p["base_usd"], usdt_rate, multiplier)
        rows.append([btn(f"{p['label']} ➜ {price:,} تومان", f"select:premium:{p['key']}")])
    rows.append([btn("آپدیت قیمت‌ها", "menu:premium", "refresh")])
    rows.append(nav_row("menu:shop"))
    return {"inline_keyboard": rows}


def stars_menu_keyboard(usdt_rate):
    multiplier = db.get_stars_multiplier()
    rows = []
    for p in db.list_packages("stars"):
        price = compute_price(p["base_usd"], usdt_rate, multiplier)
        rows.append([btn(f"{p['label']} ➜ {price:,} تومان", f"select:stars:{p['key']}")])
    rows.append([btn("آپدیت قیمت‌ها", "menu:stars", "refresh")])
    rows.append(nav_row("menu:shop"))
    return {"inline_keyboard": rows}


def _product_menu_cb(product_type):
    return "menu:premium" if product_type == "premium" else "menu:stars"


def payment_method_keyboard(order):
    """کیبورد انتخاب روش پرداخت بعد از ثبت سفارش خرید."""
    return {"inline_keyboard": [
        [btn("پرداخت از کیف پول", f"pay_wallet:{order['id']}", "wallet")],
        [btn("کارت به کارت", f"pay_card:{order['id']}", "card")],
        _discount_row(order),
        [btn("به‌روزرسانی قیمت", f"refresh_order:{order['id']}", "refresh"),
         btn("لغو سفارش", f"cancel_order:{order['id']}", "cross")],
        nav_row(_product_menu_cb(order["product_type"])),
    ]}


def card_payment_keyboard(order):
    rows = []
    if order["product_type"] == "wallet_topup":
        rows.append([btn("لغو سفارش", f"cancel_order:{order['id']}", "cross")])
        rows.append(nav_row("menu:wallet"))
    else:
        rows.append([btn("پرداخت از کیف پول", f"pay_wallet:{order['id']}", "wallet")])
        rows.append(_discount_row(order))
        rows.append([btn("به‌روزرسانی قیمت", f"refresh_order:{order['id']}", "refresh"),
                     btn("لغو سفارش", f"cancel_order:{order['id']}", "cross")])
        rows.append(nav_row(_product_menu_cb(order["product_type"])))
    return {"inline_keyboard": rows}


def expired_price_keyboard(product_type, package_key):
    return {"inline_keyboard": [
        [btn("دریافت قیمت جدید", f"select:{product_type}:{package_key}", "refresh")],
        nav_row(_product_menu_cb(product_type)),
    ]}


def wallet_menu_keyboard():
    return {"inline_keyboard": [
        [btn("شارژ کیف پول", "wallet:topup", "wallet")],
        [btn("تاریخچه تراکنش‌ها", "wallet:history", "pricelist")],
        nav_row("back:main"),
    ]}


def contact_us_keyboard():
    return {"inline_keyboard": [
        [btn("شروع تیکت پشتیبانی", "menu:support", "ticket")],
        nav_row("back:main"),
    ]}


def admin_decision_keyboard(order):
    """کیبورد تایید/رد برای ادمین. برای سفارش با احراز هویت، «رد رسید» و «رد احراز هویت» جدا هستند
    تا کاربر بفهمد دلیل رد کدام بوده. زیر همه‌ی کارت‌ها «پیام به کاربر» و «پروفایل» هم هست."""
    approve_text = "تایید و شارژ کیف پول" if order["product_type"] == "wallet_topup" else "تایید سفارش"
    if order.get("kyc_required"):
        rows = [
            [btn(approve_text, f"approve:{order['id']}", "check")],
            [btn("رد رسید", f"reject:{order['id']}:rcpt", "cross"),
             btn("رد احراز هویت", f"reject:{order['id']}:kyc", "cross")],
        ]
    else:
        rows = [[btn(approve_text, f"approve:{order['id']}", "check"),
                 btn("رد سفارش", f"reject:{order['id']}", "cross")]]
    rows.append(_msg_profile_row(order["user_id"]))
    return {"inline_keyboard": rows}


def _msg_profile_row(user_id):
    return [btn("پیام به کاربر", f"admin_msguser:{user_id}", "contact"),
            btn("پروفایل", f"admin_profile:{user_id}", "users")]


def admin_complete_keyboard(order):
    """مرحله‌ی دوم: بعد از تایید پرداخت، ادمین بعد از فعال‌سازی/واریز واقعی این دکمه را می‌زند."""
    done = "استارز واریز شد" if order["product_type"] == "stars" else "پرمیوم فعال شد"
    return {"inline_keyboard": [
        [btn(done + " — تکمیل سفارش", f"complete:{order['id']}", "check")],
        [btn("رد سفارش (بازگشت وجه)", f"reject:{order['id']}", "cross")],
    ]}


def admin_name_of(from_user):
    name = " ".join(x for x in (from_user.get("first_name"), from_user.get("last_name")) if x).strip()
    if from_user.get("username"):
        name = f"{name} (@{from_user['username']})".strip()
    return name or str(from_user.get("id"))


def admin_panel_keyboard():
    """پنل مدیریت: دقیقاً به همان چیدمان ربات وایرگارد (ردیف‌ها مطابق درخواست)."""
    try:
        counts = db.count_orders_by_status()
        pending = counts.get("pending_admin", 0) + counts.get("processing", 0)
        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_usercount", "users"),
         btn("گزارش فروش", "admin_stats", "stats")],
        [btn("تنظیم درصد رفرال", "admin_setreferral_start", "invite")],
        [btn("حد مبلغ احراز هویت", "admin_kyc_start", "card")],
        [btn("پروفایل کاربر", "admin_userlookup_start", "users")],
        [btn("کدهای تخفیف", "admin_disc_menu", "invite"), btn("هدیه‌ی شارژ", "admin_bonus_start", "wallet")],
        [btn("بنر مینی‌اپ", "admin_banner", "pricelist"), btn("سؤالات متداول", "admin_faq", "contact")],
        [btn("پیام همگانی", "admin_broadcast_start", "broadcast")],
        [btn("منوی اصلی", "back:main", "home")],
    ]}


def admin_sales_keyboard():
    """زیرمنوی «مدیریت فروش»: ضرایب و بسته‌ها."""
    pm = db.get_premium_multiplier()
    sm = db.get_stars_multiplier()
    return {"inline_keyboard": [
        [btn(f"ضریب پرمیوم فعلی: {pm}", "admin_setmult:premium", "edit")],
        [btn(f"ضریب استارز فعلی: {sm}", "admin_setmult:stars", "edit")],
        [btn("مدیریت بسته‌های پرمیوم", "admin_packages:premium", "premium")],
        [btn("مدیریت بسته‌های استارز", "admin_packages:stars", "stars")],
        [btn("▶️ شروع فروش پرمیوم" if db.get_pause("premium") else "⏸ توقف فروش پرمیوم", "admin_pause:premium", "lock")],
        [btn("▶️ شروع فروش استارز" if db.get_pause("stars") else "⏸ توقف فروش استارز", "admin_pause:stars", "lock")],
        [btn("بازگشت به پنل مدیریت", "menu:admin", "back")],
    ]}


def admin_packages_keyboard(product_type):
    rows = []
    for p in db.list_packages(product_type):
        rows.append([btn(f"{p['label']} — {p['base_usd']}$", f"admin_delpkg_ask:{p['id']}", "trash")])
    rows.append([btn("افزودن مقدار جدید", f"admin_addpkg:{product_type}", "plus")])
    rows.append([btn("بازگشت", "admin_sales", "back"), btn("پنل مدیریت", "menu:admin", "admin")])
    return {"inline_keyboard": rows}


def admin_delpkg_confirm_keyboard(package_id, product_type):
    return {"inline_keyboard": [[
        btn("بله، حذف کن", f"admin_delpkg_do:{package_id}:{product_type}", "check"),
        btn("انصراف", f"admin_packages:{product_type}", "cross"),
    ]]}


def broadcast_confirm_keyboard():
    return {"inline_keyboard": [[
        btn("بله، برای همه ارسال کن", "broadcast_send", "check"),
        btn("انصراف", "broadcast_cancel", "cross"),
    ]]}


def ticket_admin_keyboard(ticket_id, user_id=None):
    rows = [[
        btn("پاسخ", f"admin_ticket_reply:{ticket_id}", "reply"),
        btn("بستن تیکت", f"admin_ticket_close:{ticket_id}", "lock"),
    ]]
    if user_id:
        rows.append([btn("پروفایل", f"admin_profile:{user_id}", "users")])
    return {"inline_keyboard": rows}


def _mini_on():
    return getattr(config, "MINIAPP_ENABLED", True) and config.BASE_URL.startswith("https://")


MINI_ICON_ID = "5855092747270098407"


def _mini_button(route=None, text="مینی‌اپ مستر پریمیوم"):
    """دکمه‌ی مینی‌اپ: آیکون پریمیوم اول دکمه + رنگ آبی."""
    url = config.BASE_URL.rstrip("/") + "/app" + (f"?r={route}" if route else "")
    b = {"text": text, "web_app": {"url": url}}
    if getattr(config, "PREMIUM_BUTTON_ICONS", True):
        b["icon_custom_emoji_id"] = MINI_ICON_ID
    else:
        b["text"] = "🛍 " + text
    if getattr(config, "BUTTON_STYLES_ENABLED", True):
        b["style"] = "primary"
    return b


def _mini_rows(route):
    if not _mini_on():
        return []
    return [[_mini_button(route, "مشاهده در مینی‌اپ")]]


def _mini_kb(route):
    rows = _mini_rows(route)
    return {"inline_keyboard": rows} if rows else None


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")],
    ] + _mini_rows(f"ticket/{ticket_id}")}


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


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


def admin_back_keyboard():
    return {"inline_keyboard": [[btn("پنل مدیریت", "menu:admin", "admin")]]}


STATUS_LABELS = {
    "awaiting_payment": "در انتظار پرداخت",
    "awaiting_kyc": "در انتظار ارسال مدارک احراز هویت",
    "awaiting_username": "در انتظار دریافت یوزرنیم",
    "pending_admin": "در انتظار تایید ادمین",
    "processing": "پرداخت تایید شد — در حال انجام",
    "approved": "تکمیل شد",
    "rejected": "رد شد",
    "cancelled": "لغو شده",
    "expired": "منقضی شده",
}
STATUS_EMOJI = {
    "awaiting_kyc": "clock",
    "awaiting_username": "clock",
    "pending_admin": "clock",
    "processing": "clock",
    "approved": "check",
    "rejected": "cross",
}

WALLET_TX_LABELS = {
    "topup": "شارژ کیف پول",
    "purchase": "خرید",
    "refund": "بازگشت وجه سفارش",
    "referral_bonus": "پورسانت دعوت دوستان",
    "adjust": "تعدیل موجودی",
    "bonus": "هدیه شارژ کیف پول",
}


def _product_name(product_type):
    return {"premium": "پرمیوم", "stars": "استارز", "wallet_topup": "شارژ کیف پول"}.get(product_type, product_type)


def _format_wallet_tx_line(t):
    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 "-"
    return f"{when} | {label} | {sign}{abs(total):,} تومان"


def find_package(product_type, key):
    if product_type == "stars" and str(key).startswith("custom"):
        try:
            n = int(str(key)[6:])
        except ValueError:
            return None
        if config.STARS_MIN_AMOUNT <= n <= config.STARS_CUSTOM_MAX:
            fa = str(n).translate(str.maketrans("0123456789", "۰۱۲۳۴۵۶۷۸۹"))
            return {"key": f"custom{n}", "label": f"{fa} استارز", "base_usd": round(n * config.STARS_USD_PER_STAR, 4)}
        return None
    return db.get_package(product_type, key)


def _screen(chat_id, message_id, parts, kb=None):
    """صفحه را روی همان پیام ویرایش می‌کند (بدون شلوغ‌شدن چت)؛ اگر نشد پیام جدید می‌فرستد. شناسه‌ی پیام را برمی‌گرداند."""
    text, entities = build_message(parts)
    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 message_id
    resp = tg.send_message(chat_id, text, reply_markup=kb, entities=entities)
    try:
        return resp["result"]["message_id"]
    except Exception:
        return None


# ---------------------------------------------------------------------------
# صفحه‌ها (منوی اصلی، فروشگاه، کیف پول، رفرال، ارتباط با ما، سفارش‌های من)
# ---------------------------------------------------------------------------

def show_main_menu(chat_id, user_id, edit=None):
    parts = [("e", "premium"), " ", ("e", "stars"), " به ربات فروش پرمیوم و استارز تلگرام خوش آمدید.\n\n",
             "خرید امن با کارت‌به‌کارت یا ", ("e", "wallet"), " کیف پول داخلی و پشتیبانی سریع.\n\n",
             *(["🛍 برای تجربه‌ی سریع‌تر می‌توانید از «مینی‌اپ» (دکمه‌ی بالا) استفاده کنید؛ "
                "یا مثل همیشه از دکمه‌های همین ربات.\n\n"] if _mini_on() else []),
             "یکی از گزینه‌های زیر را انتخاب کنید:"]
    _screen(chat_id, edit, parts, main_menu_keyboard(user_id))


def show_shop_menu(chat_id, edit=None):
    _screen(chat_id, edit, [("e", "shop"), " ", ("b", "فروشگاه"), "\n\nلطفاً دسته‌ی مورد نظر را انتخاب کنید:"],
            shop_menu_keyboard())


def _rate_error(chat_id, edit):
    _screen(chat_id, edit, [("e", "warning"), " در حال حاضر امکان دریافت نرخ لحظه‌ای تتر نیست، چند لحظه دیگر تلاش کنید."],
            {"inline_keyboard": [nav_row("menu:shop")]})


def show_premium_menu(chat_id, edit=None):
    if db.get_pause("premium"):
        show_paused_screen(chat_id, edit, "premium")
        return
    usdt = get_usdt_price()
    if usdt is None:
        _rate_error(chat_id, edit)
        return
    parts = [("e", "premium"), " ", ("b", "خرید پرمیوم تلگرام"), "\n",
             ("e", "usdt"), f" نرخ لحظه‌ای تتر: {usdt:,.0f} تومان\n\n",
             "یکی از بسته‌ها را انتخاب کنید:"]
    _screen(chat_id, edit, parts, premium_menu_keyboard(usdt))


def show_stars_menu(chat_id, edit=None):
    if db.get_pause("stars"):
        show_paused_screen(chat_id, edit, "stars")
        return
    usdt = get_usdt_price()
    if usdt is None:
        _rate_error(chat_id, edit)
        return
    parts = [("e", "stars"), " ", ("b", "خرید استارز تلگرام"), "\n",
             ("e", "usdt"), f" نرخ لحظه‌ای تتر: {usdt:,.0f} تومان\n\n",
             "یکی از بسته‌ها را انتخاب کنید:"]
    _screen(chat_id, edit, parts, stars_menu_keyboard(usdt))


def show_wallet_menu(chat_id, user_id, edit=None):
    balance, referral = db.get_wallet(user_id)
    parts = [("e", "wallet"), " ", ("b", "کیف پول شما"), "\n\n",
             "موجودی: ", ("b", f"{balance:,} تومان"), "\n"]
    if referral > 0:
        parts.append(f"اعتبار رفرال: {referral:,} تومان\n")
    parts.append(f"قابل استفاده برای خرید: {balance + referral:,} تومان\n")
    parts.append("\nموجودی را می‌توانید برای خرید پرمیوم و استارز خرج کنید. برای افزایش موجودی «شارژ کیف پول» را بزنید.")
    parts += ["\n\n"] + _bonus_offer_parts() if _bonus_offer_parts() else []
    _screen(chat_id, edit, parts, wallet_menu_keyboard())


def show_wallet_history(chat_id, user_id, edit=None):
    txs = db.list_wallet_transactions(user_id, limit=10)
    parts = [("e", "pricelist"), " ", ("b", "تاریخچه تراکنش‌های کیف پول"), "\n\n"]
    if not txs:
        parts.append("هنوز تراکنشی ثبت نشده است.")
    else:
        for t in txs:
            parts.append(_format_wallet_tx_line(t) + "\n")
    _screen(chat_id, edit, parts, {"inline_keyboard": [nav_row("menu:wallet")]})


def show_referral_info(chat_id, user_id, edit=None):
    percent = db.get_referral_percent()
    kb = {"inline_keyboard": [nav_row("back:main")]}
    if percent <= 0:
        _screen(chat_id, edit, [("e", "invite"), " سیستم دعوت دوستان در حال حاضر توسط مدیریت غیرفعال است."], kb)
        return
    ref_count = db.count_referrals(user_id)
    _, ref_balance = db.get_wallet(user_id)
    username = get_bot_username()
    link_line = f"https://t.me/{username}?start=ref_{user_id}" if username else "لینک دعوت شما موقتاً در دسترس نیست."
    parts = [("e", "invite"), " ", ("b", "دعوت دوستان"), "\n\n",
             f"با معرفی هر دوست، وقتی خرید (پرمیوم یا استارز) انجام دهد، {percent:g}٪ از مبلغ خریدش "
             "به‌صورت اعتبار به کیف پول شما اضافه می‌شود.\n\n",
             ("e", "users"), f" تعداد دعوت‌شدگان: {ref_count}\n",
             ("e", "wallet"), f" اعتبار رفرال فعلی: {ref_balance:,} تومان\n\n",
             "لینک اختصاصی دعوت شما:\n", ("code", link_line)]
    _screen(chat_id, edit, parts, kb)


def show_contact_us(chat_id, edit=None):
    parts = [("e", "contact"), " ", ("b", "ارتباط با پشتیبانی"), "\n\n",
             "برای هرگونه سوال، راهنمایی پیش از خرید یا پیگیری سفارش، خوشحال می‌شویم پاسخگوی شما باشیم.\n\n",
             "برای ارتباط با پشتیبانی، همین‌جا یک تیکت باز کنید (سریع و قابل پیگیری):\n\n",
             ("e", "clock"), " ساعات پاسخگویی: هر روز، در سریع‌ترین زمان ممکن"]
    _screen(chat_id, edit, parts, contact_us_keyboard())


def show_my_orders(chat_id, user_id, edit=None):
    """لیست سفارش‌ها به‌صورت دکمه؛ با زدن هر دکمه جزئیات همان سفارش نمایش داده می‌شود."""
    orders = db.list_user_orders_visible(user_id, limit=10)
    parts = [("e", "pending"), " ", ("b", "سفارش‌های من"), "\n\n"]
    rows = []
    if not orders:
        parts.append("هنوز سفارشی برای نمایش وجود ندارد.")
    else:
        parts.append("برای دیدن جزئیات، روی سفارش موردنظر بزنید:")
        for o in orders:
            label = STATUS_LABELS.get(o["status"], o["status"])
            title = f"#{o['id']} · {o['package_label']} · {label}" if o["product_type"] != "wallet_topup" \
                else f"#{o['id']} · شارژ کیف پول {o['price_toman']:,} · {label}"
            rows.append([btn(title, f"myorder:{o['id']}", STATUS_EMOJI.get(o["status"]))])
    rows.append(nav_row("back:main"))
    _screen(chat_id, edit, parts, {"inline_keyboard": rows})


def show_order_detail(chat_id, user_id, order_id, edit=None):
    order = db.get_order(order_id)
    if not order or order["user_id"] != user_id:
        tg.send_message(chat_id, "سفارش پیدا نشد.")
        return
    status = order["status"]
    emoji = STATUS_EMOJI.get(status)
    is_topup = order["product_type"] == "wallet_topup"
    pay = {"wallet": "کیف پول", "card": "کارت به کارت"}.get(order.get("payment_method"), "کارت به کارت")

    parts = [("e", "pending"), " ", ("b", f"جزئیات سفارش #{order['id']}"), "\n\n",
             f"نوع: {_product_name(order['product_type'])}\n"]
    if not is_topup:
        parts.append(f"بسته: {order['package_label']}\n")
        if order.get("target_username"):
            parts.append(f"یوزرنیم مقصد: @{order['target_username']}\n")
    parts += [f"مبلغ: {order['price_toman']:,} تومان" + (f" (با کد تخفیف {order['discount_code']})" if order.get("discount_code") else "") + "\n",
              f"روش پرداخت: {pay}\n",
              f"تاریخ ثبت: {_fmt_ts(order['created_at'])}\n",
              "وضعیت: "]
    if emoji:
        parts += [("e", emoji), " "]
    parts.append(STATUS_LABELS.get(status, status) + "\n")
    if status == "approved" and order.get("completed_at"):
        parts.append(f"تاریخ تکمیل: {_fmt_ts(order['completed_at'])}\n")
    elif status == "rejected" and order.get("decided_at"):
        parts.append(f"تاریخ رد: {_fmt_ts(order['decided_at'])}\n")

    notes = {
        "awaiting_username": "\nرسید دریافت شده؛ یوزرنیم مقصد را بفرستید.",
        "pending_admin": "\nسفارش شما در صف بررسی ادمین است.",
        "processing": "\nپرداخت شما تایید شده و سفارش در حال انجام است؛ بعد از تکمیل به شما اطلاع می‌دهیم.",
        "rejected": "\nدر صورت داشتن سوال از بخش «ارتباط با ما» با پشتیبانی تماس بگیرید."
                    + ("\nمبلغ سفارش به کیف پول شما برگشت داده شده است."
                       if order.get("wallet_refunded") else ""),
    }
    if status in notes:
        parts.append(notes[status])
    rows = []
    if not is_topup and status == "approved":
        rows.append([btn("خرید مجدد", f"reorder:{order['id']}", "refresh")])
    rows.append(nav_row("menu:myorders"))
    _screen(chat_id, edit, parts, {"inline_keyboard": rows})


def show_admin_panel(chat_id, edit=None):
    _screen(chat_id, edit, [("e", "admin"), " ", ("b", "پنل مدیریت"), "\n\nبخش مورد نظر را انتخاب کنید:"],
            admin_panel_keyboard())


def show_admin_sales(chat_id, edit=None):
    _screen(chat_id, edit, [("e", "admin"), " ", ("b", "مدیریت فروش"), "\n\n",
                            "ضرایب قیمت و بسته‌های پرمیوم/استارز را از اینجا مدیریت کنید:"],
            admin_sales_keyboard())


# ---------------------------------------------------------------------------
# منطق اصلی سفارش
# ---------------------------------------------------------------------------

def create_order_and_show_payment(chat_id, from_user, product_type, package_key, edit=None, carry_code=None):
    if db.get_pause(product_type):
        show_paused_screen(chat_id, edit, product_type)
        return
    pkg = find_package(product_type, package_key)
    if not pkg:
        tg.send_message(chat_id, "این بسته دیگر موجود نیست.")
        return
    usdt = get_usdt_price()
    if usdt is None:
        _rate_error(chat_id, None)
        return
    multiplier = db.get_premium_multiplier() if product_type == "premium" else db.get_stars_multiplier()
    price = compute_price(pkg["base_usd"], usdt, multiplier)

    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(),
        product_type=product_type,
        package_key=pkg["key"],
        package_label=pkg["label"],
        base_usd=pkg["base_usd"],
        multiplier=multiplier,
        usdt_rate=usdt,
        price_toman=price,
    )
    if carry_code:
        db.apply_discount(order_id, from_user["id"], carry_code)
    order = db.get_order(order_id)
    show_order_payment(chat_id, edit, order)


def _order_is_stale(order):
    if order["product_type"] == "wallet_topup":
        return time.time() - order["created_at"] > config.TOPUP_VALIDITY_SECONDS
    return time.time() - order["created_at"] > config.PRICE_VALIDITY_SECONDS


def show_card_payment(chat_id, message_id, order):
    """اطلاعات کارت‌به‌کارت برای یک سفارش (خرید یا شارژ کیف پول)."""
    is_topup = order["product_type"] == "wallet_topup"
    if is_topup:
        parts = [("e", "wallet"), f" درخواست شارژ کیف پول (سفارش #{order['id']})\n\n",
                 "مبلغ: ", ("b", f"{order['price_toman']:,} تومان"), "\n\n",
                 ("e", "clock"), f" مهلت واریز: {config.TOPUP_VALIDITY_SECONDS // 60} دقیقه\n\n"]
    else:
        validity_min = config.PRICE_VALIDITY_SECONDS // 60
        parts = [("e", "card"), f" پرداخت کارت به کارت — سفارش #{order['id']}\n\n",
                 f"بسته: {order['package_label']}\n",
                 *_price_parts(order), "\n\n",
                 ("e", "clock"), f" این قیمت تا {validity_min} دقیقه پس از ثبت سفارش معتبر است.\n\n"]
    parts += ["لطفاً مبلغ فوق را به شماره کارت زیر واریز کنید:\n",
              ("code", config.CARD_NUMBER),
              f"\nبه نام: {config.CARD_HOLDER}\n\n",
              "سپس عکس رسید پرداخت را همین‌جا برای من ارسال کنید."]
    mid = _screen(chat_id, message_id, parts, card_payment_keyboard(order))
    db.set_order_screen(order["id"], mid)


def handle_pay_wallet(chat_id, message_id, user_id, cq_id, order_id):
    order = db.get_order(order_id)
    if not order or order["user_id"] != user_id or order["product_type"] == "wallet_topup":
        tg.answer_callback_query(cq_id, "سفارش پیدا نشد.", show_alert=True)
        return
    if order["status"] != "awaiting_payment":
        tg.answer_callback_query(cq_id, "این سفارش دیگر قابل پرداخت نیست.", show_alert=True)
        return
    if _order_is_stale(order):
        db.expire_order_atomic(order_id)
        tg.answer_callback_query(cq_id)
        _screen(chat_id, message_id,
                [("e", "clock"), f" اعتبار قیمت سفارش #{order_id} تمام شده است (چون نرخ تتر ممکن است تغییر کرده باشد).\n"
                 "با دکمه‌ی زیر قیمت تازه بگیرید:"],
                expired_price_keyboard(order["product_type"], order["package_key"]))
        return

    if db.get_pause(order["product_type"]):
        tg.answer_callback_query(cq_id, "فروش این بخش موقتاً متوقف است.", show_alert=True)
        return
    result, info = db.pay_order_with_wallet(order_id, user_id)
    if result == "unavailable":
        tg.answer_callback_query(cq_id, "این سفارش دیگر قابل پرداخت نیست.", show_alert=True)
        return
    if result == "insufficient":
        available, price = info
        tg.answer_callback_query(cq_id)
        parts = [("e", "cross"), " موجودی کافی نیست.\n\n",
                 f"موجودی قابل استفاده: {available:,} تومان\n",
                 f"مبلغ سفارش: {price:,} تومان\n",
                 f"کسری: {price - available:,} تومان\n\n",
                 "می‌توانید کیف پول را شارژ کنید یا با کارت به کارت پرداخت کنید.\n",
                 ("e", "clock"), f" (قیمت سفارش تا {config.PRICE_VALIDITY_SECONDS // 60} دقیقه پس از ثبت معتبر است؛ "
                 "بعد از شارژ در صورت نیاز سفارش را دوباره بگیرید.)"]
        kb = {"inline_keyboard": [
            [btn("شارژ کیف پول", "wallet:topup", "wallet")],
            [btn("کارت به کارت", f"pay_card:{order_id}", "card")],
            [btn("لغو سفارش", f"cancel_order:{order_id}", "cross")],
            nav_row(_product_menu_cb(order["product_type"])),
        ]}
        _screen(chat_id, message_id, parts, kb)
        return

    new_balance, new_referral = info
    tg.answer_callback_query(cq_id, "پرداخت انجام شد ✅")
    balance_line = f"موجودی کیف پول: {new_balance:,} تومان"
    if new_referral > 0:
        balance_line += f"\nاعتبار رفرال باقی‌مانده: {new_referral:,} تومان"
    _screen(chat_id, message_id,
            [("e", "check"), f" مبلغ {order['price_toman']:,} تومان از کیف پول شما کسر شد (سفارش #{order_id}).\n",
             balance_line + "\n\n",
             "حالا یوزرنیم تلگرامی که می‌خواهید این خرید برایش فعال شود را بفرستید "
             "(با یا بدون @، مثال: @username)."],
            _username_prompt_kb(order))
    db.set_order_screen(order_id, message_id)


def handle_receipt_photo(chat_id, from_user, message):
    user_id = from_user["id"]
    order = db.get_latest_user_order_in_status(user_id, "awaiting_payment")
    kyc_order = db.get_latest_user_order_in_status(user_id, "awaiting_kyc")
    if kyc_order and (not order or kyc_order["id"] > order["id"]):
        # رسید قبلاً ثبت شده؛ این عکس یکی از مدارک احراز هویت (کارت بانکی / کارت ملی) است
        _handle_kyc_upload(message, from_user, kyc_order)
        return
    if not order:
        tg.send_message(chat_id, "سفارش فعالی برای دریافت رسید از شما پیدا نکردم. از منوی فروشگاه یک بسته انتخاب کنید "
                                 "یا از کیف پول، شارژ بزنید.\n\n"
                                 "اگر برای سفارشی که لغو یا منقضی شده واریز انجام داده‌اید، لطفاً از «ارتباط با ما» "
                                 "یک تیکت باز کنید و رسید را همان‌جا بفرستید تا پیگیری شود.",
                        reply_markup={"inline_keyboard": [[btn("منوی اصلی", "back:main", "home")]]})
        return

    is_topup = order["product_type"] == "wallet_topup"

    if _order_is_stale(order):
        db.expire_order_atomic(order["id"])
        if is_topup:
            tg.send_message(chat_id, f"⏰ مهلت واریز سفارش شارژ #{order['id']} تمام شده است. اگر واریز کرده‌اید با پشتیبانی "
                                     "تماس بگیرید؛ در غیر این صورت دوباره شارژ بزنید.",
                            reply_markup={"inline_keyboard": [[btn("شارژ کیف پول", "wallet:topup", "wallet")],
                                                              nav_row("menu:wallet")]})
        else:
            tg.send_message(
                chat_id,
                f"⏰ اعتبار قیمت سفارش #{order['id']} تمام شده و رسید پذیرفته نمی‌شود "
                f"(چون نرخ تتر ممکن است تغییر کرده باشد).\n"
                "با دکمه‌ی زیر قیمت تازه بگیرید و دوباره واریز کنید:",
                reply_markup=expired_price_keyboard(order["product_type"], order["package_key"]),
            )
        return

    photo_sizes = message.get("photo") or []
    if not photo_sizes:
        tg.send_message(chat_id, "لطفاً رسید را به‌صورت عکس ارسال کنید.")
        return
    largest = photo_sizes[-1]
    file_id = largest["file_id"]
    unique_id = largest["file_unique_id"]

    dup = db.find_order_by_receipt_unique_id(unique_id)
    if dup:
        tg.send_message(
            chat_id,
            "❌ این رسید قبلاً برای یک سفارش دیگر ثبت شده و پذیرفته نمی‌شود. "
            "اگر فکر می‌کنید اشتباهی رخ داده با ادمین تماس بگیرید.",
        )
        alert = [
            ("e", "warning"), " رسید تکراری شناسایی شد!\n",
            f"سفارش فعلی: #{order['id']} (کاربر {user_id})\n",
            f"همان رسید قبلاً برای سفارش #{dup['id']} ثبت شده بود.",
        ]
        for admin_id in config.ADMIN_IDS:
            try:
                send_rich(admin_id, alert)
            except Exception:
                logger.exception("خطا در اطلاع‌رسانی رسید تکراری به ادمین")
        return

    needs_kyc = kyc_needed(order)
    try:
        attached = db.attach_receipt(order["id"], file_id, unique_id, kyc_required=needs_kyc)
    except sqlite3.IntegrityError:
        tg.send_message(chat_id, "❌ این رسید قبلاً ثبت شده است.")
        return
    if not attached:
        tg.send_message(chat_id, "این سفارش دیگر در انتظار پرداخت نیست.")
        return

    if needs_kyc:
        # مبلغ بالاتر از حد احراز هویت: تا ارسال مدارک، سفارش برای بررسی/تایید به ادمین‌ها نمی‌رود
        fresh = db.get_order(order["id"])
        _send_kyc_prompt(chat_id, fresh)
        _notify_admins_waiting_kyc(fresh)
        return

    if is_topup:
        _replace(chat_id, order.get("screen_mid"),
                 ["✅ رسید شما دریافت شد و در حال بررسی توسط ادمین است. لطفاً منتظر بمانید؛ "
                  "بعد از تایید، کیف پول شما شارژ می‌شود."],
                 {"inline_keyboard": [nav_row("menu:wallet")]})
        notify_admins_new_order(db.get_order(order["id"]))
        return

    mid = _replace(chat_id, order.get("screen_mid"),
                   ["✅ رسید دریافت شد.\n\n"
                    "لطفاً یوزرنیم تلگرامی که می‌خواهید این خرید برایش فعال شود را بفرستید "
                    "(با یا بدون @، مثال: @username)."],
                   _username_prompt_kb(order))
    db.set_order_screen(order["id"], mid)


# ---------------------------------------------------------------------------
# احراز هویت برای مبالغ بالا (هم‌سبک ربات وایرگارد)
# ---------------------------------------------------------------------------

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 _notify_admins_waiting_kyc(order):
    """به ادمین‌ها خبر می‌دهد رسیدی آمده ولی منتظر مدارک احراز هویت هستیم (فعلاً بدون دکمه‌ی تایید/رد)."""
    parts = _order_admin_caption_parts(order) + [
        "\n", ("e", "clock"),
        f" این پرداخت بیشتر از {get_kyc_threshold():,} تومان است؛ منتظر دریافت احراز هویت "
        "(عکس کارت بانکی و کارت ملی) از کاربر هستم. بعد از ارسال مدارک، دکمه‌های تایید/رد برایتان می‌آید.",
    ]
    text, entities = build_message(parts)
    kb = {"inline_keyboard": [_msg_profile_row(order["user_id"])]}
    for admin_id in config.ADMIN_IDS:
        try:
            if order.get("receipt_file_id"):
                tg.send_photo_by_file_id(admin_id, order["receipt_file_id"], caption=text, reply_markup=kb)
            else:
                tg.send_message(admin_id, text, entities=entities, reply_markup=kb)
        except Exception:
            logger.exception("خطا در اطلاع‌رسانی «منتظر احراز هویت» به ادمین %s", admin_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)} عکس):", parse_mode=None)
    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(callback_query, order_id):
    cq_id = callback_query["id"]
    user_id = callback_query["from"]["id"]
    message = callback_query.get("message") or {}
    chat_id = message["chat"]["id"]
    order = db.get_order(order_id)
    if not order or order["user_id"] != user_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.confirm_kyc_atomic(order_id):
        tg.answer_callback_query(cq_id, "مدارک این سفارش قبلاً ارسال شده است.", show_alert=True)
        return
    tg.answer_callback_query(cq_id)
    fresh = db.get_order(order_id)

    if fresh["product_type"] == "wallet_topup":
        _screen(chat_id, message.get("message_id"),
                [("e", "check"), " مدارک شما دریافت شد.\n\n",
                 "اکنون منتظر ", ("b", "تایید رسید و احراز هویت"), " توسط پشتیبانی باشید؛ "
                 "بعد از تایید، کیف پول شما شارژ می‌شود و نتیجه همین‌جا اطلاع داده می‌شود."],
                {"inline_keyboard": [nav_row("menu:wallet")]})
        notify_admins_new_order(fresh)
        return

    # خرید پرمیوم/استارز: بعد از تایید مدارک، یوزرنیم مقصد پرسیده می‌شود و سپس سفارش به ادمین می‌رسد
    _screen(chat_id, message.get("message_id"),
            [("e", "check"), " مدارک شما دریافت شد.\n\n",
             "حالا یوزرنیم تلگرامی که می‌خواهید این خرید برایش فعال شود را بفرستید "
             "(با یا بدون @، مثال: @username).\n\n",
             "بعد از آن سفارش برای ", ("b", "تایید رسید و احراز هویت"), " به پشتیبانی می‌رسد."],
            None)


USERNAME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9_]{3,31}$")


def handle_username_text(chat_id, from_user, text):
    user_id = from_user["id"]
    order = db.get_latest_user_order_in_status(user_id, "awaiting_username")
    if not order:
        return False  # این پیام مربوط به مرحله‌ی یوزرنیم نبود

    candidate = text.strip().lstrip("@")
    if not USERNAME_RE.match(candidate):
        tg.send_message(
            chat_id,
            "یوزرنیم نامعتبر است. یوزرنیم تلگرام باید بین ۵ تا ۳۲ کاراکتر، با حروف انگلیسی شروع شود "
            "و فقط شامل حروف/عدد/زیرخط باشد. دوباره بفرستید (مثال: @username).",
        )
        return True

    db.attach_target_username(order["id"], candidate)
    order = db.get_order(order["id"])

    tg.send_message(
        chat_id,
        f"✅ سفارش #{order['id']} برای بررسی "
        f"{'رسید و احراز هویت ' if order.get('kyc_required') else ''}به ادمین ارسال شد. به‌محض تایید، "
        f"{'پرمیوم' if order['product_type'] == 'premium' else 'استارز'} به یوزرنیم @{candidate} اضافه می‌شود.",
        reply_markup={"inline_keyboard": [[btn("سفارش‌های من", "menu:myorders", "pending"),
                                           btn("منوی اصلی", "back:main", "home")]]},
    )
    notify_admins_new_order(order)
    return True


def _order_admin_caption_parts(order):
    if order["product_type"] == "wallet_topup":
        parts = [
            ("e", "wallet"), f" درخواست شارژ کیف پول — سفارش #{order['id']}\n\n",
            f"کاربر: {order['full_name'] or '-'} (@{order['username'] or '—'}) — آیدی عددی: {order['user_id']}\n",
            f"مبلغ شارژ: {order['price_toman']:,} تومان\n",
        ]
    else:
        emoji_key = "premium" if order["product_type"] == "premium" else "stars"
        pay_line = "کیف پول (بدون رسید)" if order.get("payment_method") == "wallet" else "کارت به کارت (با رسید)"
        parts = [
            ("e", emoji_key), f" سفارش جدید #{order['id']}\n\n",
            f"کاربر: {order['full_name'] or '-'} (@{order['username'] or '—'}) — آیدی عددی: {order['user_id']}\n",
            f"بسته: {order['package_label']}\n",
            f"مبلغ: {order['price_toman']:,} تومان\n",
            f"روش پرداخت: {pay_line}\n",
            f"نرخ تتر هنگام ثبت: {order['usdt_rate']:,.0f} تومان\n",
        ]
        if order.get("target_username"):
            parts.append(f"یوزرنیم مقصد: @{order['target_username']}\n")
    if order.get("discount_code"):
        parts.append(f"🏷 کد تخفیف: {order['discount_code']} (−{order.get('discount_amount') or 0:,} تومان از {order.get('original_price') or 0:,})\n")
    if order.get("kyc_required"):
        n = len(db.get_kyc_files(order["id"]))
        parts.append(f"🪪 احراز هویت: مبلغ بالاتر از حد مجاز بود — {n} عکس مدرک دریافت شد (بالای همین پیام).\n"
                     if order["status"] != "awaiting_kyc" else "🪪 احراز هویت: لازم است (هنوز مدارک نرسیده).\n")
    return parts


def notify_admins_new_order(order):
    caption_text, caption_entities = build_message(_order_admin_caption_parts(order))
    message_ids = {}
    for admin_id in config.ADMIN_IDS:
        try:
            if order.get("kyc_required"):
                _send_kyc_files_to_admin(admin_id, order)  # اول مدارک، بعد کارت اصلی با دکمه‌ها
            if order.get("receipt_file_id"):
                resp = tg.send_photo_by_file_id(
                    admin_id, order["receipt_file_id"], caption=caption_text,
                    reply_markup=admin_decision_keyboard(order),
                )
            else:  # پرداخت با کیف پول: رسیدی وجود ندارد
                resp = tg.send_message(admin_id, caption_text, entities=caption_entities,
                                       reply_markup=admin_decision_keyboard(order))
            if resp and resp.get("ok"):
                message_ids[str(admin_id)] = resp["result"]["message_id"]
        except Exception:
            logger.exception("خطا در ارسال سفارش جدید به ادمین %s", admin_id)
    db.set_admin_message_ids(order["id"], json.dumps(message_ids))


def refresh_admin_messages(order, status_parts, keyboard_for=None):
    """پیام سفارش را برای «همه‌ی ادمین‌ها» ویرایش می‌کند تا وضعیت لحظه‌ای را نشان دهد
    (مثلاً «ادمین X تایید کرد و در حال رسیدگی است» / «تکمیل شد»).
    keyboard_for: تابعی (admin_id) -> کیبورد یا None؛ None یعنی دکمه‌ها حذف شود."""
    try:
        message_ids = json.loads(order.get("admin_message_ids") or "{}")
    except Exception:
        message_ids = {}
    text, entities = build_message(_order_admin_caption_parts(order) + ["\n"] + status_parts)
    for admin_id_str, message_id in message_ids.items():
        admin_id = int(admin_id_str)
        kb = keyboard_for(admin_id) if keyboard_for else None
        markup = kb if kb else {"inline_keyboard": []}
        try:
            if order.get("receipt_file_id"):
                resp = tg.edit_message_caption(admin_id, message_id, text, entities=entities, reply_markup=markup)
            else:
                resp = tg.edit_message_text(admin_id, message_id, text, entities=entities, reply_markup=markup)
            if resp and not resp.get("ok") and "not modified" not in str(resp.get("description", "")):
                logger.warning("ویرایش پیام ادمین %s ناموفق بود: %s", admin_id, resp)
        except Exception:
            logger.exception("خطا در ویرایش پیام سفارش برای ادمین %s", admin_id)


# ---------------------------------------------------------------------------
# کیف پول: شارژ
# ---------------------------------------------------------------------------

def start_wallet_topup(chat_id, user_id, message_id, cq_id):
    tg.answer_callback_query(cq_id)
    db.set_state(user_id, "wallet_topup_amount", {"mid": message_id})
    parts = [("e", "wallet"), " ", ("b", "شارژ کیف پول"), "\n\n",
             "مبلغی که می‌خواهید کیف پول خود را شارژ کنید را به تومان و فقط به‌صورت عدد ارسال کنید.\n",
             f"حداقل: {config.TOPUP_MIN_TOMAN:,} تومان\n",
             "مثال: 100000\n\n", *_bonus_offer_parts(),
             "(برای انصراف، /cancel را بزنید یا «بازگشت»)"]
    _screen(chat_id, message_id, parts, {"inline_keyboard": [nav_row("menu:wallet")]})


def handle_topup_amount_entry(chat_id, from_user, text, state=None, user_msg_id=None):
    mid = ((state or {}).get("data") or {}).get("mid")
    if mid:
        _del(chat_id, user_msg_id)  # فقط وقتی می‌توانیم همان پیام را ویرایش کنیم
    kb = {"inline_keyboard": [nav_row("menu:wallet")]}

    def err(msg):
        _screen(chat_id, mid, [("e", "cross"), f" {msg}\n\n(برای انصراف «بازگشت» را بزنید)"], kb)

    cleaned = normalize_digits(text).replace(",", "").replace("،", "").strip()
    if not cleaned.isdigit():
        return err("لطفاً مبلغ را فقط به‌صورت عدد (تومان) ارسال کنید. مثلاً: 100000")
    amount = int(cleaned)
    if amount < config.TOPUP_MIN_TOMAN:
        return err(f"حداقل مبلغ شارژ کیف پول {config.TOPUP_MIN_TOMAN:,} تومان است. لطفاً مبلغ را دوباره ارسال کنید.")
    if amount > config.TOPUP_MAX_TOMAN:
        return err(f"حداکثر مبلغ شارژ {config.TOPUP_MAX_TOMAN:,} تومان است. لطفاً مبلغ کمتری بفرستید.")

    db.clear_state(from_user["id"])
    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(),
        product_type="wallet_topup",
        package_key="topup",
        package_label="شارژ کیف پول",
        base_usd=0,
        multiplier=1,
        usdt_rate=None,
        price_toman=amount,
    )
    show_card_payment(chat_id, mid, db.get_order(order_id))


# ---------------------------------------------------------------------------
# رفرال
# ---------------------------------------------------------------------------

def maybe_register_referral(message):
    """اگر پیام /start ref_<id> باشد معرف را ثبت می‌کند. قبل از چک عضویت اجباری صدا زده می‌شود
    تا کاربری که هنوز عضو کانال نیست هم معرفش از دست نرود."""
    text = (message.get("text") or "").strip()
    if not (text == "/start" or text.startswith("/start ")):
        return
    payload = text[len("/start"):].strip()
    if not payload.startswith("ref_"):
        return
    ref_part = payload[len("ref_"):]
    if not ref_part.isdigit():
        return
    user_id = (message.get("from") or {}).get("id")
    referrer_id = int(ref_part)
    if user_id and db.set_referred_by(user_id, referrer_id):
        try:
            tg.send_message(referrer_id, "🎉 یک کاربر جدید با لینک دعوت شما وارد ربات شد!")
        except Exception:
            pass


def apply_referral_bonus(order):
    """بعد از تایید سفارش خرید، درصد رفرال را به اعتبار دعوت‌کننده اضافه می‌کند (دقیقاً یک‌بار)."""
    if order["product_type"] not in ("premium", "stars"):
        return
    referrer_id = db.get_referred_by(order["user_id"])
    percent = db.get_referral_percent()
    if not referrer_id or percent <= 0:
        return
    bonus = int(order["price_toman"] * percent / 100)
    if bonus <= 0:
        return
    if db.credit_referral_once(order["id"], referrer_id, bonus):
        try:
            send_rich(referrer_id, [
                ("e", "invite"), f" یکی از دعوت‌شدگان شما خرید کرد و {bonus:,} تومان اعتبار رفرال به کیف پول شما اضافه شد.",
            ])
        except Exception:
            logger.exception("خطا در اطلاع‌رسانی پورسانت رفرال")


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


# ---------------------------------------------------------------------------
# تصمیم ادمین (تایید / رد)
# ---------------------------------------------------------------------------

def _already_decided_text(order):
    if order["status"] == "processing":
        who = order.get("decided_by_name") or "ادمین دیگر"
        return f"این سفارش را قبلاً {who} تایید کرده و در حال رسیدگی است."
    if order["status"] == "approved":
        who = order.get("completed_by_name") or order.get("decided_by_name") or "ادمین دیگر"
        return f"این سفارش قبلاً توسط {who} تکمیل شده."
    if order["status"] == "rejected":
        who = order.get("decided_by_name") or "ادمین دیگر"
        return f"این سفارش قبلاً توسط {who} رد شده."
    return "این سفارش دیگر قابل تصمیم‌گیری نیست."


def handle_approve(callback_query, order_id):
    """تایید پرداخت. خرید: pending_admin -> processing (مرحله‌ی اول). شارژ کیف پول: مستقیم تکمیل."""
    admin_id = callback_query["from"]["id"]
    admin_name = admin_name_of(callback_query["from"])
    order = db.get_order(order_id)
    if not order:
        tg.answer_callback_query(callback_query["id"], "سفارش پیدا نشد.", show_alert=True)
        return

    if order["product_type"] == "wallet_topup":
        res = db.approve_topup_atomic(order_id, admin_id, admin_name)
        if not res:
            tg.answer_callback_query(callback_query["id"], _already_decided_text(db.get_order(order_id)), show_alert=True)
            return
        user_id, amount, new_balance = res
        thr, bonus = db.get_topup_bonus()
        bonus_given = 0
        if bonus > 0 and thr > 0 and amount >= thr:
            new_balance = db.add_user_balance(user_id, bonus, kind="bonus", order_id=order_id, note="هدیه شارژ")
            bonus_given = bonus
        tg.answer_callback_query(callback_query["id"], "تایید شد ✅ کیف پول شارژ شد")
        refresh_admin_messages(db.get_order(order_id), [
            ("e", "check"), f" تایید شد توسط {admin_name} — کیف پول کاربر {amount:,} تومان شارژ شد." + (f" (+{bonus_given:,} هدیه)" if bonus_given else ""),
        ])
        send_rich(user_id, [
            ("e", "check"), " رسید شما تایید شد و کیف پول‌تان به مبلغ ", ("b", f"{amount:,} تومان"), " شارژ شد.\n",
            (f"🎁 هدیه‌ی شارژ: {bonus_given:,} تومان اضافه شد.\n" if bonus_given else ""),
            f"موجودی جدید: {new_balance:,} تومان",
        ], reply_markup=_mini_kb("wallet"))
        return

    ok = db.decide_order_atomic(order_id, "processing", admin_id, admin_name=admin_name)
    if not ok:
        tg.answer_callback_query(callback_query["id"], _already_decided_text(db.get_order(order_id)), show_alert=True)
        return
    tg.answer_callback_query(callback_query["id"], "پرداخت تایید شد ✅")
    order = db.get_order(order_id)
    what = "پرمیوم" if order["product_type"] == "premium" else "استارز"

    # ادمینی که تایید کرد: دکمه‌ی «تکمیل»؛ بقیه‌ی ادمین‌ها: فقط اطلاع که کی رسیدگی می‌کند
    refresh_admin_messages(
        order,
        [("e", "clock"), f" پرداخت توسط {admin_name} تایید شد و در حال رسیدگی است.\n",
         f"بعد از {'واریز استارز' if what == 'استارز' else 'فعال‌سازی پرمیوم'} برای @{order['target_username']} "
         "دکمه‌ی «تکمیل سفارش» را بزنید تا به کاربر اطلاع داده شود (فقط ادمینی که تایید کرده)."],
        keyboard_for=lambda aid: admin_complete_keyboard(order) if aid == admin_id else None,
    )
    buyer_emoji = "premium" if order["product_type"] == "premium" else "stars"
    send_rich(order["user_id"], [
        ("e", buyer_emoji), f" پرداخت سفارش #{order_id} شما تایید شد ✅\n\n",
        f"{what} در حال فعال‌سازی برای @{order['target_username']} است. "
        "به‌محض تکمیل، همین‌جا به شما اطلاع می‌دهیم.",
    ], reply_markup=_mini_kb(f"order/{order_id}"))


def handle_complete(callback_query, order_id):
    """مرحله‌ی دوم: ادمین بعد از فعال‌سازی/واریز واقعی می‌زند -> processing -> approved و پیام به کاربر."""
    admin_id = callback_query["from"]["id"]
    admin_name = admin_name_of(callback_query["from"])
    order = db.get_order(order_id)
    if not order:
        tg.answer_callback_query(callback_query["id"], "سفارش پیدا نشد.", show_alert=True)
        return
    if order["status"] == "processing" and order.get("decided_by") not in (None, admin_id):
        who = order.get("decided_by_name") or "ادمین دیگر"
        tg.answer_callback_query(callback_query["id"], f"این سفارش را {who} تایید کرده و باید همان ادمین تکمیلش کند.",
                                 show_alert=True)
        return
    if not db.complete_order_atomic(order_id, admin_id, admin_name):
        tg.answer_callback_query(callback_query["id"], _already_decided_text(order), show_alert=True)
        return
    tg.answer_callback_query(callback_query["id"], "سفارش تکمیل شد 🎉")
    order = db.get_order(order_id)
    what = "پرمیوم" if order["product_type"] == "premium" else "استارز"
    refresh_admin_messages(order, [("e", "check"), f" سفارش تکمیل شد (توسط {admin_name}) و به کاربر اطلاع داده شد."])
    buyer_emoji = "premium" if order["product_type"] == "premium" else "stars"
    send_rich(order["user_id"], [
        ("e", buyer_emoji), f" سفارش #{order_id} شما تکمیل شد 🎉\n\n",
        f"{what} برای یوزرنیم @{order['target_username']} با موفقیت "
        f"{'فعال' if what == 'پرمیوم' else 'واریز'} شد. از خرید شما ممنونیم!",
    ], reply_markup={"inline_keyboard": [[btn("سفارش‌های من", "menu:myorders", "pending"),
                                          btn("منوی اصلی", "back:main", "home")]] + _mini_rows(f"order/{order_id}")})
    apply_referral_bonus(order)


_REJECT_KIND_LABELS = {"rcpt": "رسید", "kyc": "احراز هویت"}


def handle_reject_start(callback_query, order_id, kind=None):
    """kind: None (رد عادی) | 'rcpt' (رد رسید) | 'kyc' (رد احراز هویت) — فقط برای سفارش‌های دارای احراز هویت."""
    admin_id = callback_query["from"]["id"]
    order = db.get_order(order_id)
    if not order or order["status"] not in ("pending_admin", "processing"):
        tg.answer_callback_query(callback_query["id"], _already_decided_text(order) if order else "سفارش پیدا نشد.", show_alert=True)
        return
    kind = kind if kind in _REJECT_KIND_LABELS else None
    db.set_state(admin_id, f"reject_reason:{order_id}" + (f":{kind}" if kind else ""))
    tg.answer_callback_query(callback_query["id"])
    what = f"رد {_REJECT_KIND_LABELS[kind]}" if kind else "رد"
    tg.send_message(
        callback_query["message"]["chat"]["id"],
        f"دلیل {what} سفارش #{order_id} را بنویسید (یا فقط «-» بفرستید اگر دلیلی نیست):",
    )


def finalize_reject(admin_id, order_id, reason, admin_name=None, kind=None):
    order = db.get_order(order_id)
    if not order:
        return
    admin_name = admin_name or str(admin_id)
    ok = db.reject_order_atomic(order_id, admin_id, admin_name)
    if not ok:
        tg.send_message(admin_id, _already_decided_text(db.get_order(order_id)))
        return
    order = db.get_order(order_id)
    kind_label = _REJECT_KIND_LABELS.get(kind)
    refresh_admin_messages(order, [("e", "cross"),
                                   f" سفارش رد شد ({'رد ' + kind_label if kind_label else 'رد سفارش'}) توسط {admin_name}."
                                   + ("" if reason.strip() in ("", "-") else f"\nدلیل: {reason.strip()}")])
    refunded = db.refund_wallet_order(order_id)  # فقط اگر با کیف پول پرداخت شده بود
    tg.send_message(admin_id, f"سفارش #{order_id} رد شد." + (f"\nمبلغ {refunded:,} تومان به کیف پول کاربر برگشت." if refunded else ""))
    reason_text = "" if reason.strip() == "-" else f"\nدلیل: {reason.strip()}"
    refund_text = f"\n\n💰 مبلغ {refunded:,} تومان به کیف پول شما برگشت داده شد." if refunded else ""
    what = "درخواست شارژ کیف پول" if order["product_type"] == "wallet_topup" else "سفارش"
    if kind == "kyc":
        head = (f"❌ متاسفانه احراز هویت شما برای {what} #{order_id} تایید نشد.\n"
                "دلیل کلی: مدارک ارسالی (عکس کارت بانکی / کارت ملی) ناقص، نامعتبر یا مطابق با صاحب کارتِ پرداخت‌کننده نبود.")
    elif kind == "rcpt":
        head = (f"❌ متاسفانه رسید {what} #{order_id} شما تایید نشد.\n"
                "دلیل کلی: رسید ارسالی نامعتبر، ناخوانا یا مغایر با پرداخت بود.")
    else:
        head = f"❌ متاسفانه {what} #{order_id} شما رد شد."
    tg.send_message(
        order["user_id"],
        f"{head}{reason_text}{refund_text}\n\nدر صورت داشتن سوال از «ارتباط با ما» با پشتیبانی در ارتباط باشید.",
        parse_mode=None,
    )


# ---------------------------------------------------------------------------
# پنل ادمین: تغییر ضریب قیمت
# ---------------------------------------------------------------------------

def start_set_multiplier(callback_query, product_type):
    admin_id = callback_query["from"]["id"]
    db.set_state(admin_id, f"multiplier:{product_type}")
    current = db.get_premium_multiplier() if product_type == "premium" else db.get_stars_multiplier()
    tg.answer_callback_query(callback_query["id"])
    tg.send_message(
        callback_query["message"]["chat"]["id"],
        f"ضریب فعلی «{'پرمیوم' if product_type == 'premium' else 'استارز'}»: {current}\n"
        f"عدد ضریب جدید را بفرستید (مثلاً 1.15):",
        reply_markup={"inline_keyboard": [[btn("بازگشت", "admin_sales", "back")]]},
    )


def finalize_set_multiplier(admin_id, product_type, text):
    try:
        value = float(normalize_digits(text).strip().replace(",", "."))
        if value <= 0 or value > 5:
            raise ValueError
    except ValueError:
        tg.send_message(admin_id, "عدد نامعتبر است. یک عدد مثل 1.15 بفرستید.")
        return False
    if product_type == "premium":
        db.set_premium_multiplier(value)
    else:
        db.set_stars_multiplier(value)
    tg.send_message(admin_id, f"✅ ضریب {'پرمیوم' if product_type=='premium' else 'استارز'} روی {value} تنظیم شد.",
                    reply_markup=admin_sales_keyboard())
    return True


# ---------------------------------------------------------------------------
# پنل ادمین: افزودن/حذف بسته (مقدار دلخواه)
# ---------------------------------------------------------------------------

def finalize_add_package(admin_id, product_type, text):
    parts = normalize_digits(text).strip().split()
    if product_type == "stars":
        if len(parts) != 1:
            tg.send_message(admin_id, "فقط یک عدد بفرستید: تعداد استارز. مثال: 300")
            return
        try:
            amount = int(parts[0])
        except ValueError:
            tg.send_message(admin_id, "عدد نامعتبر است.")
            return
        if amount < config.STARS_MIN_AMOUNT:
            tg.send_message(admin_id, f"حداقل تعداد مجاز {config.STARS_MIN_AMOUNT} استارز است.")
            return
        base_usd = round(amount * config.STARS_USD_PER_STAR, 4)
        key = str(amount)
        label = f"{amount:,} استارز"
        pkg_id = db.add_package("stars", key, label, amount, base_usd)
        if pkg_id is None:
            tg.send_message(admin_id, "بسته‌ای با همین تعداد استارز از قبل در لیست موجود است.")
            return
        tg.send_message(admin_id, f"✅ بسته‌ی «{label}» اضافه شد (قیمت پایه {base_usd}$).",
                        reply_markup=admin_packages_keyboard("stars"))
        return

    # premium
    if len(parts) != 2:
        tg.send_message(admin_id, "دو عدد با فاصله بفرستید: تعداد ماه و قیمت پایه به دلار. مثال: 9 25")
        return
    try:
        months = int(parts[0])
        price = float(parts[1].replace(",", "."))
    except ValueError:
        tg.send_message(admin_id, "ورودی نامعتبر است.")
        return
    if months <= 0 or price <= 0:
        tg.send_message(admin_id, "مقادیر باید مثبت باشند.")
        return
    key = str(months)
    label = f"{months} ماهه"
    pkg_id = db.add_package("premium", key, label, months, price)
    if pkg_id is None:
        tg.send_message(admin_id, "بسته‌ای با همین تعداد ماه از قبل موجود است.")
        return
    tg.send_message(admin_id, f"✅ بسته‌ی «{label}» اضافه شد (قیمت پایه {price}$).",
                    reply_markup=admin_packages_keyboard("premium"))


# ---------------------------------------------------------------------------
# پنل ادمین: آمار فروش
# ---------------------------------------------------------------------------

def _send_sales_report(chat_id):
    now = int(time.time())
    periods = [
        ("۲۴ ساعت گذشته", now - 24 * 3600),
        ("۷ روز گذشته", now - 7 * 24 * 3600),
        ("۳۰ روز گذشته", now - 30 * 24 * 3600),
        ("مجموع کل", 0),
    ]
    lines = []
    all_time = None
    for label, start in periods:
        s = db.get_sales_stats(start, now)
        if start == 0:
            all_time = s
        lines.append(f"{label}: {s['count']} سفارش — {s['total']:,} تومان")
    top_line = ""
    if all_time and all_time.get("top_package"):
        top = all_time["top_package"]
        top_line = f"\n\nپرفروش‌ترین بسته: {top['package_label']} ({'پرمیوم' if top['product_type']=='premium' else 'استارز'})"
    parts = [("e", "stats"), " ", ("b", "گزارش فروش (فقط سفارش‌های تایید‌شده؛ شارژ کیف پول جزو فروش نیست)"),
             "\n\n", "\n".join(lines), top_line]
    text, entities = build_message(parts)
    tg.send_message(chat_id, text, entities=entities, reply_markup=admin_back_keyboard())


# ---------------------------------------------------------------------------
# پنل ادمین: پیام همگانی (Broadcast) — با copyMessage، هر نوع پیامی را عیناً برای همه می‌فرستد
# ---------------------------------------------------------------------------

def handle_broadcast_content_received(chat_id, admin_id, message):
    message_id = message["message_id"]
    aud = ((db.get_state(admin_id) or {}).get("data") or {}).get("aud", "all")
    db.set_state(admin_id, "broadcast_confirm", {"message_id": message_id, "aud": aud})
    user_count = len(db.list_audience_ids(aud))
    tg.send_message(
        chat_id,
        f"پیام بالا دقیقاً همین‌طور برای {user_count:,} کاربر ارسال می‌شود. مطمئنید؟",
        reply_markup=broadcast_confirm_keyboard(),
    )


def _run_broadcast(admin_id, message_id, aud="all"):
    user_ids = db.list_audience_ids(aud)
    success, fail = 0, 0
    for uid in user_ids:
        if uid == admin_id:
            continue
        try:
            result = tg.copy_message(uid, admin_id, message_id)
        except Exception:
            result = None
        if result and result.get("ok"):
            success += 1
        else:
            fail += 1
        time.sleep(0.05)
    return success, fail


# ---------------------------------------------------------------------------
# سیستم تیکت پشتیبانی (هم‌سبک ربات وایرگارد)
# ---------------------------------------------------------------------------

# نوع‌های مدیایی که کاربر/ادمین می‌تواند داخل تیکت بفرستد
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 _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 _notify_other_admins_ticket(ticket_id, acting_admin_id, from_user, action_text, body=None, with_buttons=True):
    """وقتی یک ادمین به تیکتی پاسخ می‌دهد یا آن را می‌بندد، به بقیه‌ی ادمین‌ها خبر می‌دهد."""
    who = admin_name_of(from_user) if from_user else str(acting_admin_id)
    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 {}
        kb = ticket_admin_keyboard(ticket_id, ticket.get("user_id"))
    for admin_id in config.ADMIN_IDS:
        if admin_id == acting_admin_id:
            continue
        try:
            tg.send_message(admin_id, text, reply_markup=kb, entities=entities)
        except Exception:
            logger.exception("خطا در اطلاع‌رسانی تیکت به ادمین %s", admin_id)


def handle_ticket_user_message(chat_id, user_id, from_user, ticket_id, text, msg_id=None, media=None):
    """پیام (متن یا مدیا) کاربر داخل یک تیکت پشتیبانی را ثبت و برای ادمین‌ها ارسال می‌کند.
    media: (نوع, file_id, کپشن) یا None."""
    ticket = db.get_ticket(ticket_id) if ticket_id else None
    if not ticket or ticket["status"] != "open":
        db.clear_state(user_id)
        tg.send_message(chat_id, "این گفتگوی پشتیبانی دیگر باز نیست. برای شروع تیکت جدید، «ارتباط با ما» را از منو بزنید.",
                        reply_markup={"inline_keyboard": [[btn("ارتباط با ما", "menu:contact", "contact"),
                                                           btn("منوی اصلی", "back:main", "home")]]})
        return

    first_message = not db.ticket_has_user_message(ticket_id)
    logged = text
    if media:
        logged = f"[{TICKET_MEDIA_LABELS[media[0]]}] {media[2]}".strip()
    db.add_ticket_message(ticket_id, "user", logged)
    # وضعیت باز می‌ماند تا کاربر بتواند پیام‌های بعدی را هم بدون فشردن دوباره‌ی دکمه بفرستد
    db.set_state(user_id, "support_chat", {"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}) | آیدی: {user_id}\n\n",
        f"«{logged}»",
    ])
    kb = ticket_admin_keyboard(ticket_id, user_id)
    for admin_id in config.ADMIN_IDS:
        try:
            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])
        except Exception:
            logger.exception("خطا در اطلاع‌رسانی پیام تیکت به ادمین %s", admin_id)

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


def finalize_ticket_admin_reply(admin_id, ticket_id, text, from_user=None, reply_msg_id=None):
    """پاسخ متنیِ ادمین به یک تیکت پشتیبانی را ثبت و برای کاربر ارسال می‌کند."""
    ticket = db.get_ticket(ticket_id)
    if not ticket or ticket["status"] != "open":
        db.clear_state(admin_id)
        tg.send_message(admin_id, "این تیکت دیگر باز نیست.")
        return
    db.add_ticket_message(ticket_id, "admin", text)
    # وضعیت باز می‌ماند تا ادمین بتواند بدون فشردن دوباره‌ی «پاسخ»، به گفتگو ادامه دهد
    db.set_state(admin_id, f"ticket_reply:{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_id, "⚠️ پیام برای کاربر ارسال نشد (احتمالاً ربات را بلاک کرده). "
                                  "پاسخ در تاریخچه‌ی تیکت ثبت شد.")
        return
    _ack_message(admin_id, reply_msg_id, "✅ پیام ارسال شد.")
    _notify_other_admins_ticket(ticket_id, admin_id, from_user, "به این تیکت پاسخ داد", body=text)


def handle_ticket_admin_media(admin_id, message, state, from_user=None):
    """ادمین در حالت پاسخ به تیکت (یا «پیام به کاربر») عکس/ویدیو/صوت/ویس/فایل فرستاده؛
    همان مدیا (با کپشنش) برای کاربر ارسال و در تاریخچه‌ی تیکت ثبت می‌شود."""
    action = state.get("action") or ""
    data = state.get("data") or {}
    if action == "support_msg":
        ticket_id = _ticket_for_admin_message(data.get("uid"))
        if ticket_id is None:
            db.clear_state(admin_id)
            tg.send_message(admin_id, "کاربر پیدا نشد.")
            return
    else:
        ticket_id = int(action.split(":", 1)[1])
    ticket = db.get_ticket(ticket_id)
    if not ticket or ticket["status"] != "open":
        db.clear_state(admin_id)
        tg.send_message(admin_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_id, "⚠️ ارسال برای کاربر ناموفق بود (احتمالاً ربات را بلاک کرده یا فایل قابل ارسال نیست).")
        return

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


def _ticket_for_admin_message(uid):
    """تیکت باز کاربر را برمی‌گرداند (اگر نبود، یک تیکت جدید می‌سازد). اگر کاربر وجود نداشته باشد None."""
    if not uid or not db.get_user(uid):
        return None
    ticket = db.get_open_ticket_for_user(uid)
    return ticket["id"] if ticket else db.create_ticket(uid)


def handle_support_message_to_user(admin_id, text, state, from_user=None, reply_msg_id=None):
    """ادمین از طریق ربات به یک کاربر پیام می‌دهد: همان سیستم تیکت (تیکت باز کاربر یا یک تیکت جدید)."""
    ticket_id = _ticket_for_admin_message((state.get("data") or {}).get("uid"))
    if ticket_id is None:
        db.clear_state(admin_id)
        tg.send_message(admin_id, "کاربر پیدا نشد.")
        return
    finalize_ticket_admin_reply(admin_id, ticket_id, text, from_user, reply_msg_id)


def _start_support_msg(admin_id, uid):
    db.set_state(admin_id, "support_msg", {"uid": uid})
    tg.send_message(
        admin_id,
        f"✍️ پیامی را که می‌خواهید از طرف پشتیبانی برای کاربر {uid} بفرستید، همین‌جا بنویسید "
        f"(متن، عکس، ویدیو، ویس، صوت یا فایل).\n"
        f"پیام با عنوان «پیام از پشتیبانی» ارسال می‌شود و کاربر با دکمه‌ی «پاسخ به پشتیبانی» می‌تواند جواب بدهد؛ "
        f"جوابش برایتان می‌آید.\n\n(برای انصراف، /cancel)",
        parse_mode=None,
    )


def _resolve_user_lookup(text):
    """آیدی عددی یا یوزرنیم را به user_id تبدیل می‌کند (یا None)."""
    raw = normalize_digits(text.strip()).lstrip("@")
    if raw.isdigit():
        uid = int(raw)
        return uid if db.get_user(uid) else None
    found = db.find_user_by_username(raw)
    return found["user_id"] if found else None


def show_admin_user_profile(admin_id, uid, edit=None):
    """پروفایل یک‌جای کاربر برای ادمین: مشخصات، کیف پول، آخرین سفارش‌ها، تیکت‌ها و دکمه‌ها."""
    user = db.get_user(uid)
    if not user:
        tg.send_message(admin_id, "کاربری با این مشخصات پیدا نشد (باید حداقل یک‌بار ربات را استارت کرده باشد).")
        return False
    balance, ref = db.get_wallet(uid)
    parts = [("e", "users"), " ", ("b", "پروفایل کاربر"), "\n",
             f"{user.get('full_name') or '-'} (@{user.get('username') or '-'})\n",
             f"آیدی: ", ("code", str(uid)), "\n"]
    if user.get("created_at"):
        parts.append(f"عضویت: {_fmt_ts(user['created_at'])}\n")
    parts += [f"\n💰 موجودی کیف پول: {balance:,} تومان\n",
              f"🎁 اعتبار رفرال: {ref:,} تومان | دعوت‌شده‌ها: {db.count_referrals(uid)}\n"]

    orders = db.list_user_orders(uid, limit=5)
    parts.append("\n🧾 آخرین سفارش‌ها:\n")
    if orders:
        for o in orders:
            title = o["package_label"] if o["product_type"] != "wallet_topup" else "شارژ کیف پول"
            parts.append(f"#{o['id']} • {_product_name(o['product_type'])} • {title} • {o['price_toman']:,} • "
                         f"{STATUS_LABELS.get(o['status'], o['status'])}\n")
    else:
        parts.append("سفارشی ثبت نشده.\n")

    tickets = db.list_tickets_for_user(uid, limit=3)
    parts.append("\n🎫 تیکت‌ها:\n")
    open_ticket = None
    if tickets:
        for t in tickets:
            msgs = db.list_ticket_messages(t["id"], limit=50)
            last = (msgs[-1]["text"] or "")[:50] if msgs else "-"
            parts.append(f"#{t['id']} ({'باز' if t['status'] == 'open' else 'بسته'}) — {last}\n")
            if t["status"] == "open" and not open_ticket:
                open_ticket = t
    else:
        parts.append("تیکتی ندارد.\n")

    rows = [[btn("پیام به کاربر", f"admin_msguser:{uid}", "contact"), btn("ویرایش موجودی", f"admin_walletedit:{uid}", "wallet")]]
    for o in orders:
        if o["status"] in _CANCELLABLE:
            rows.append([btn(f"سفارش باز #{o['id']} — مشاهده/لغو", f"admin_order:{o['id']}", "pending")])
    if open_ticket:
        rows.append([btn(f"پاسخ به تیکت #{open_ticket['id']}", f"admin_ticket_reply:{open_ticket['id']}", "reply"),
                     btn("بستن تیکت", f"admin_ticket_close:{open_ticket['id']}", "lock")])
    rows.append([btn("پنل مدیریت", "menu:admin", "admin")])
    text, entities = build_message(parts)
    if edit:
        resp = tg.edit_message_text(admin_id, edit, text, reply_markup={"inline_keyboard": rows}, entities=entities)
        if resp.get("ok") or "not modified" in str(resp.get("description", "")):
            return True
    tg.send_message(admin_id, text, reply_markup={"inline_keyboard": rows}, entities=entities)
    return True


def _send_open_tickets_to_admin(chat_id):
    """لیست تیکت‌های باز برای ادمین (دکمه‌ی «تیکت‌های باز» و دستور /tickets)."""
    tickets = db.list_open_tickets()
    if not tickets:
        tg.send_message(chat_id, "تیکت بازی وجود ندارد.", reply_markup=admin_back_keyboard())
        return
    for t in tickets[:20]:
        msgs = db.list_ticket_messages(t["id"], limit=50)
        waiting = bool(msgs) and msgs[-1]["sender"] == "user"
        last = "\n".join(f"[{'کاربر' if m['sender'] == 'user' else 'ادمین'}] {(m['text'] or '')[:80]}" for m in msgs[-5:])
        head = "🟡 منتظر پاسخ شما" if waiting else "✅ پاسخ داده شده"
        tg.send_message(chat_id, f"🎫 تیکت #{t['id']} — کاربر {t['user_id']} — {head}\n\n{last}",
                        reply_markup=ticket_admin_keyboard(t["id"], t["user_id"]), parse_mode=None)
    tg.send_message(chat_id, "پایان لیست تیکت‌ها.", reply_markup=admin_back_keyboard())


# ---------------------------------------------------------------------------
# دیسپچر آپدیت‌ها
# ---------------------------------------------------------------------------

# ---------------------------------------------------------------------------
# (جدید) کد تخفیف، توقف فروش، هدیه‌ی شارژ، ابزار ادمین، جایگزینی پیام‌ها
# ---------------------------------------------------------------------------
_CANCELLABLE = ("awaiting_payment", "awaiting_kyc", "awaiting_username", "pending_admin")
PRODUCT_FA = {"premium": "پرمیوم", "stars": "استارز"}
DEFAULT_PAUSE_TEXT = "فروش به‌صورت موقت متوقف است. لطفاً بعداً دوباره سر بزنید."
DISC_ERRORS = {
    "invalid": "کد تخفیف نامعتبر است.",
    "expired": "این کد تخفیف منقضی شده است.",
    "exhausted": "ظرفیت استفاده از این کد تمام شده است.",
    "used": "شما قبلاً از این کد استفاده کرده‌اید.",
    "order": "این سفارش دیگر قابل ویرایش نیست.",
}


def _del(chat_id, mid):
    if mid:
        try:
            tg.delete_message(chat_id, mid)
        except Exception:
            pass


def _replace(chat_id, old_mid, parts, kb=None):
    """پیام جدید را (پایین چت) می‌فرستد و پیام قبلی را پاک می‌کند؛ شناسه‌ی پیام جدید را برمی‌گرداند."""
    text, entities = build_message(parts)
    resp = tg.send_message(chat_id, text, reply_markup=kb, entities=entities)
    new_mid = None
    try:
        new_mid = resp["result"]["message_id"]
    except Exception:
        pass
    if new_mid and old_mid:
        _del(chat_id, old_mid)
    return new_mid


def _discount_row(order):
    if order.get("discount_code"):
        return [btn(f"حذف کد تخفیف ({order['discount_code']})", f"disc_remove:{order['id']}", "trash")]
    return [btn("کد تخفیف دارم", f"disc:{order['id']}", "invite")]


def _price_parts(order):
    parts = []
    if order.get("discount_code"):
        parts += [f"قیمت اصلی: {order.get('original_price') or 0:,} تومان\n",
                  ("e", "invite"), f" تخفیف ({order['discount_code']}): −{order.get('discount_amount') or 0:,} تومان\n"]
    parts += ["مبلغ قابل پرداخت: ", ("b", f"{order['price_toman']:,} تومان")]
    return parts


def show_order_payment(chat_id, edit, order):
    validity_min = config.PRICE_VALIDITY_SECONDS // 60
    emoji_key = "premium" if order["product_type"] == "premium" else "stars"
    parts = [
        ("e", emoji_key), f" سفارش شما ثبت شد (شماره سفارش: #{order['id']})\n\n",
        f"بسته: {order['package_label']}\n",
        *_price_parts(order), "\n\n",
        ("e", "clock"),
        f" توجه: این قیمت فقط تا {validity_min} دقیقه پس از ثبت سفارش معتبر است. اگر دیرتر پرداخت کنید، "
        "سفارش پذیرفته نمی‌شود و باید از دکمه‌ی «به‌روزرسانی قیمت» یک سفارش تازه بگیرید.\n\n",
        "روش پرداخت را انتخاب کنید:",
    ]
    mid = _screen(chat_id, edit, parts, payment_method_keyboard(order))
    db.set_order_screen(order["id"], mid)


def show_paused_screen(chat_id, edit, product):
    txt = db.get_pause(product) or DEFAULT_PAUSE_TEXT
    parts = [("e", "warning"), " ", ("b", f"فروش {PRODUCT_FA.get(product, product)} موقتاً متوقف است"), "\n\n", txt]
    kb = {"inline_keyboard": [
        [btn("وقتی فروش شروع شد خبرم کن", f"notify:{product}", "bell")],
        nav_row("menu:shop"),
    ]}
    _screen(chat_id, edit, parts, kb)


def _bonus_offer_parts():
    thr, amt = db.get_topup_bonus()
    if thr > 0 and amt > 0:
        return [("e", "invite"), f" هدیه‌ی شارژ: با هر شارژ حداقل {thr:,} تومان، {amt:,} تومان هدیه به کیف پول‌تان اضافه می‌شود.\n\n"]
    return []


def _username_prompt_kb(order):
    return None  # کاربر یوزرنیم را خودش وارد می‌کند (بدون دکمه‌ی پیشنهادی)


def _finalize_username(chat_id, order, candidate, mid, via_text):
    db.attach_target_username(order["id"], candidate)
    order = db.get_order(order["id"])
    parts = [
        f"✅ سفارش #{order['id']} برای بررسی "
        f"{'رسید و احراز هویت ' if order.get('kyc_required') else ''}به ادمین ارسال شد. به‌محض تایید، "
        f"{'پرمیوم' if order['product_type'] == 'premium' else 'استارز'} به یوزرنیم @{candidate} اضافه می‌شود."]
    kb = {"inline_keyboard": [[btn("سفارش‌های من", "menu:myorders", "pending"),
                               btn("منوی اصلی", "back:main", "home")]]}
    new = _replace(chat_id, mid, parts, kb) if via_text else _screen(chat_id, mid, parts, kb)
    db.set_order_screen(order["id"], new)
    notify_admins_new_order(order)


def handle_username_text(chat_id, from_user, text, user_msg_id=None):
    user_id = from_user["id"]
    order = db.get_latest_user_order_in_status(user_id, "awaiting_username")
    if not order:
        return False  # این پیام مربوط به مرحله‌ی یوزرنیم نبود
    _del(chat_id, user_msg_id)
    candidate = text.strip().lstrip("@")
    if not USERNAME_RE.match(candidate):
        mid = _replace(chat_id, order.get("screen_mid"), [
            ("e", "warning"), " یوزرنیم نامعتبر است. یوزرنیم تلگرام باید بین ۵ تا ۳۲ کاراکتر، با حروف انگلیسی شروع شود "
            "و فقط شامل حروف/عدد/زیرخط باشد. دوباره بفرستید (مثال: @username)."], _username_prompt_kb(order))
        db.set_order_screen(order["id"], mid)
        return True
    _finalize_username(chat_id, order, candidate, order.get("screen_mid"), via_text=True)
    return True


def handle_discount_text(chat_id, from_user, text, state, user_msg_id):
    uid = from_user["id"]
    data = state.get("data") or {}
    oid, mid = data.get("order_id"), data.get("mid")
    _del(chat_id, user_msg_id)
    res, info = db.apply_discount(oid, uid, normalize_digits(text.strip()))
    order = db.get_order(oid) if oid else None
    if res == "ok":
        db.clear_state(uid)
        show_order_payment(chat_id, mid, order)
        return
    if res == "order":
        db.clear_state(uid)
        _screen(chat_id, mid, [DISC_ERRORS["order"]], {"inline_keyboard": [nav_row("menu:shop")]})
        return
    msg = (f"این کد فقط برای خرید {PRODUCT_FA.get(info, '')} است." if res == "product" else DISC_ERRORS.get(res, "کد نامعتبر است."))
    _screen(chat_id, mid, [("e", "cross"), f" {msg}\n\nدوباره کد را بفرستید یا برگردید."],
            {"inline_keyboard": [[btn("بازگشت", f"disc_back:{oid}", "back")]]})


# ----------------------------- ادمین: تخفیف‌ها -----------------------------
def show_admin_discounts(chat_id, edit=None):
    items = db.list_discounts()
    parts = [("e", "invite"), " ", ("b", "کدهای تخفیف"), "\n\n"]
    rows = []
    if not items:
        parts.append("هنوز کدی ساخته نشده است.")
    for d in items:
        val = f"{d['value']}٪" if d["kind"] == "percent" else f"{d['value']:,} تومان"
        cap = f"{d['used']}/{d['max_uses']}" if d["max_uses"] else f"{d['used']}/∞"
        exp = _fmt_ts(d["expires_at"]) if d["expires_at"] else "بدون انقضا"
        prod = PRODUCT_FA.get(d["product"], "همه")
        parts.append(f"{'🟢' if d['active'] else '⚪'} {d['code']} — {val} — استفاده {cap} — {prod} — {exp}\n")
        rows.append([btn(f"{'غیرفعال' if d['active'] else 'فعال'} {d['code']}", f"admin_disc_toggle:{d['code']}", "lock"),
                     btn("حذف", f"admin_disc_del:{d['code']}", "trash")])
    rows.append([btn("کد جدید", "admin_disc_new", "plus")])
    rows.append([btn("پنل مدیریت", "menu:admin", "admin")])
    _screen(chat_id, edit, parts, {"inline_keyboard": rows})


def parse_discount_spec(text):
    tokens = normalize_digits(text).replace("٪", "%").split()
    if len(tokens) < 2:
        return None, "فرمت درست نیست."
    code = tokens[0].upper()
    if not re.fullmatch(r"[A-Z0-9_]{3,20}", code):
        return None, "کد باید ۳ تا ۲۰ حرف انگلیسی/عدد/زیرخط باشد."
    v = tokens[1].replace(",", "")
    if v.endswith("%"):
        if not v[:-1].isdigit() or not (1 <= int(v[:-1]) <= 90):
            return None, "درصد باید بین ۱ تا ۹۰ باشد."
        kind, value = "percent", int(v[:-1])
    elif v.isdigit() and int(v) > 0:
        kind, value = "fixed", int(v)
    else:
        return None, "مقدار تخفیف را به‌صورت 10% یا مبلغ تومان بنویسید."
    spec = {"code": code, "kind": kind, "value": value, "max_uses": 0, "expires_at": 0, "product": "all", "once": 1}
    for t in tokens[2:]:
        tl = t.lower()
        if tl.startswith("max=") and tl[4:].isdigit():
            spec["max_uses"] = int(tl[4:])
        elif tl.startswith("days=") and tl[5:].isdigit():
            spec["expires_at"] = int(time.time()) + int(tl[5:]) * 86400 if int(tl[5:]) else 0
        elif tl in ("premium", "پرمیوم"):
            spec["product"] = "premium"
        elif tl in ("stars", "استارز"):
            spec["product"] = "stars"
        elif tl in ("multi", "چندبار"):
            spec["once"] = 0
        else:
            return None, f"«{t}» را نشناختم."
    return spec, None


DISC_HELP = ("مثال‌ها:\n"
             "NOW10 10%\n"
             "VIP 50000 max=20 days=7 premium\n\n"
             "• عدد با % = درصدی، بدون % = مبلغ ثابت (تومان)\n"
             "• max=تعداد کل استفاده، days=مدت اعتبار (روز)\n"
             "• premium یا stars = محدود به یک محصول\n"
             "• multi = هر کاربر چندبار بتواند استفاده کند (پیش‌فرض: یک‌بار)")


def finalize_discount_new(admin_id, chat_id, text, state, user_msg_id):
    mid = (state.get("data") or {}).get("mid")
    _del(chat_id, user_msg_id)
    spec, err = parse_discount_spec(text)
    if spec and not db.create_discount(spec["code"], spec["kind"], spec["value"], spec["max_uses"],
                                       spec["expires_at"], spec["product"], spec["once"]):
        err = "این کد قبلاً ساخته شده است."
    if err:
        _screen(chat_id, mid, [("e", "cross"), f" {err}\n\n{DISC_HELP}"], {"inline_keyboard": [[btn("بازگشت", "admin_disc_menu", "back")]]})
        return
    db.clear_state(admin_id)
    show_admin_discounts(chat_id, mid)


# ----------------------------- ادمین: سفارش‌ها -----------------------------
def show_admin_pending(chat_id, edit=None, page=0):
    items = [("pending", o) for o in db.list_pending_orders(200)]
    items += [("kyc", o) for o in db.list_awaiting_kyc_orders(200)]
    items += [("processing", o) for o in db.list_processing_orders(200)]
    if not items:
        _screen(chat_id, edit, ["سفارش در انتظاری وجود ندارد."], admin_back_keyboard())
        return
    per = 6
    pages = (len(items) + per - 1) // per
    page = min(max(page, 0), pages - 1)
    tags = {"pending": "تایید", "kyc": "مدارک", "processing": "تکمیل"}
    rows = []
    for kind, o in items[page * per:(page + 1) * per]:
        who = "شارژ" if o["product_type"] == "wallet_topup" else o["package_label"]
        rows.append([btn(f"#{o['id']} · {who} · {o['price_toman']:,} · {tags[kind]}", f"admin_order:{o['id']}",
                         STATUS_EMOJI.get(o["status"]))])
    nav = []
    if page > 0:
        nav.append(btn("قبلی", f"admin_pending:{page - 1}", "back"))
    if page < pages - 1:
        nav.append(btn("بعدی", f"admin_pending:{page + 1}", "reply"))
    if nav:
        rows.append(nav)
    rows.append([btn("پنل مدیریت", "menu:admin", "admin")])
    _screen(chat_id, edit, [("e", "pending"), " ", ("b", f"سفارش‌های در انتظار ({len(items)})"),
                            f"\nصفحه {page + 1} از {pages}\n\nروی هر سفارش بزنید:"], {"inline_keyboard": rows})


def show_admin_order(chat_id, edit, order_id):
    order = db.get_order(order_id)
    if not order:
        _screen(chat_id, edit, ["سفارش پیدا نشد."], admin_back_keyboard())
        return
    parts = _order_admin_caption_parts(order) + [f"وضعیت: {STATUS_LABELS.get(order['status'], order['status'])}\n",
                                                 f"ثبت: {_fmt_ts(order['created_at'])}\n"]
    rows = []
    if order["status"] in _CANCELLABLE:
        rows.append([btn("لغو سفارش", f"admin_cancelorder:{order_id}", "cross")])
    if order.get("receipt_file_id"):
        rows.append([btn("نمایش رسید", f"admin_receipt:{order_id}", "pending")])
    rows.append(_msg_profile_row(order["user_id"]))
    rows.append([btn("بازگشت", "admin_pending:0", "back"), btn("پنل مدیریت", "menu:admin", "admin")])
    _screen(chat_id, edit, parts, {"inline_keyboard": rows})


def do_admin_cancel(admin_id, admin_name, order_id):
    order = db.get_order(order_id)
    if not order or not db.admin_cancel_order(order_id):
        return False
    refunded = db.refund_wallet_order(order_id)
    order = db.get_order(order_id)
    refresh_admin_messages(order, [("e", "cross"), f" سفارش توسط {admin_name} لغو شد."])
    msg = [("e", "cross"), f" سفارش #{order_id} توسط پشتیبانی لغو شد."]
    if refunded:
        msg.append(f"\n💰 مبلغ {refunded:,} تومان به کیف پول شما برگشت.")
    try:
        send_rich(order["user_id"], msg, {"inline_keyboard": [nav_row("back:main")]})
    except Exception:
        logger.exception("اطلاع لغو سفارش به کاربر ناموفق بود")
    return True


def start_wallet_edit(admin_id, chat_id, uid, mid):
    balance, ref = db.get_wallet(uid)
    for o in db.list_user_orders_by_type(uid, "wallet_topup", limit=3):
        if o.get("receipt_file_id"):
            try:
                tg.send_photo_by_file_id(admin_id, o["receipt_file_id"],
                                         caption=f"رسید شارژ #{o['id']} · {o['price_toman']:,} تومان · "
                                                 f"{STATUS_LABELS.get(o['status'], o['status'])} · {_fmt_ts(o['created_at'])}")
            except Exception:
                logger.exception("ارسال رسید برای ویرایش موجودی ناموفق بود")
    new = _replace(chat_id, mid, [("e", "wallet"), " ", ("b", "ویرایش موجودی"), f"\n\nموجودی فعلی: {balance:,} تومان\n\n",
                                  "موجودی نهاییِ درست را (به تومان، فقط عدد) بفرستید. بعد از ثبت، دقیقاً همین مقدار روی کیف پول می‌نشیند.\n",
                                  "(۳ رسید آخر شارژ این کاربر بالا فرستاده شد.)"],
                   {"inline_keyboard": [[btn("انصراف", f"admin_profile:{uid}", "back")]]})
    db.set_state(admin_id, f"wallet_edit:{uid}", {"mid": new})


def finalize_wallet_edit(admin_id, chat_id, uid, text, state, user_msg_id):
    mid = (state.get("data") or {}).get("mid")
    _del(chat_id, user_msg_id)
    cleaned = normalize_digits(text.strip()).replace(",", "").replace("٬", "").replace("،", "").replace(" ", "")
    if not cleaned.isdigit():
        _screen(chat_id, mid, [("e", "cross"), " فقط یک عدد (تومان) بفرستید، مثلاً 150000 (یا 0)."],
                {"inline_keyboard": [[btn("انصراف", f"admin_profile:{uid}", "back")]]})
        return
    db.set_user_balance(uid, int(cleaned), note=f"ویرایش توسط ادمین {admin_id}")
    db.clear_state(admin_id)
    show_admin_user_profile(admin_id, uid, edit=mid)


# ----------------------------- ادمین: توقف فروش / هدیه شارژ -----------------------------
def _notify_resume_job(product):
    for uid in db.list_notify(product):
        try:
            send_rich(uid, [("e", "check"), f" فروش {PRODUCT_FA.get(product, product)} دوباره فعال شد؛ از فروشگاه می‌توانید خرید کنید."],
                      {"inline_keyboard": [[btn("فروشگاه", "menu:shop", "shop", style="success")]]})
        except Exception:
            logger.exception("اطلاع‌رسانی شروع فروش ناموفق بود")
        db.del_notify(uid, product)
        time.sleep(0.05)


def finalize_pause_reason(admin_id, chat_id, product, text, state, user_msg_id):
    mid = (state.get("data") or {}).get("mid")
    _del(chat_id, user_msg_id)
    t = text.strip()
    db.set_pause(product, DEFAULT_PAUSE_TEXT if t in ("-", "") else t)
    db.clear_state(admin_id)
    show_admin_sales(chat_id, mid)


def finalize_bonus_cfg(admin_id, chat_id, text, state, user_msg_id):
    mid = (state.get("data") or {}).get("mid")
    _del(chat_id, user_msg_id)
    nums = normalize_digits(text).replace(",", "").replace("٬", "").replace("،", "").split()
    kb = {"inline_keyboard": [[btn("پنل مدیریت", "menu:admin", "admin")]]}
    if nums == ["0"]:
        db.set_topup_bonus(0, 0)
        db.clear_state(admin_id)
        _screen(chat_id, mid, [("e", "check"), " هدیه‌ی شارژ غیرفعال شد."], kb)
        return
    if len(nums) != 2 or not all(n.isdigit() and int(n) > 0 for n in nums):
        _screen(chat_id, mid, [("e", "cross"), " دو عدد بفرستید: حداقل شارژ و مبلغ هدیه (تومان). مثال: 1000000 50000\n(برای غیرفعال‌کردن: 0)"], kb)
        return
    db.set_topup_bonus(int(nums[0]), int(nums[1]))
    db.clear_state(admin_id)
    _screen(chat_id, mid, [("e", "check"), f" ثبت شد: با شارژ حداقل {int(nums[0]):,} تومان، {int(nums[1]):,} تومان هدیه."], kb)


# ----------------------------- ادمین: پیام همگانی هدفمند -----------------------------
AUD_LABELS = {"all": "همه‌ی کاربران", "premium": "خریداران پرمیوم", "stars": "خریداران استارز",
              "never": "استارت زده ولی نخریده"}


def _broadcast_job(admin_id, chat_id, message_id, aud):
    try:
        success, fail = _run_broadcast(admin_id, message_id, aud)
        tg.send_message(chat_id, f"📢 پیام همگانی ({AUD_LABELS.get(aud, aud)}) تمام شد.\n✅ ارسال موفق: {success}\n❌ ناموفق: {fail}",
                        reply_markup=admin_back_keyboard())
    except Exception:
        logger.exception("خطا در ارسال پیام همگانی")


# ----------------------------- ادمین: بنر و سؤالات متداول مینی‌اپ -----------------------------
def _faq_items():
    import miniapp
    return [list(x) for x in miniapp._faq()]


def show_admin_banner(chat_id, edit=None):
    cur = db.get_setting("mini_banner", "") or ""
    parts = [("e", "pricelist"), " ", ("b", "بنر مینی‌اپ"), "\n\nدر بالای صفحه‌ی خانه‌ی مینی‌اپ نشان داده می‌شود (اطلاعیه، پیشنهاد، کد تخفیف و...).\n\n",
             f"متن فعلی:\n{cur}" if cur else "فعلاً بنری فعال نیست."]
    rows = [[btn("تغییر متن بنر", "admin_banner_edit", "reply")]]
    if cur:
        rows.append([btn("حذف بنر", "admin_banner_clear", "trash")])
    rows.append([btn("پنل مدیریت", "menu:admin", "admin")])
    _screen(chat_id, edit, parts, {"inline_keyboard": rows})


def show_admin_faq(chat_id, edit=None):
    items = _faq_items()
    parts = [("e", "contact"), " ", ("b", "سؤالات متداول مینی‌اپ"), "\n\n"]
    rows = []
    for i, (q, _a) in enumerate(items):
        parts.append(f"{i + 1}. {q}\n")
        rows.append([btn(f"حذف سؤال {i + 1}", f"admin_faq_del:{i}", "trash")])
    rows.append([btn("افزودن سؤال", "admin_faq_add", "plus")])
    rows.append([btn("بازگشت به پیش‌فرض", "admin_faq_reset", "refresh")])
    rows.append([btn("پنل مدیریت", "menu:admin", "admin")])
    _screen(chat_id, edit, parts, {"inline_keyboard": rows})


def finalize_banner_edit(admin_id, chat_id, text, state, user_msg_id):
    mid = (state.get("data") or {}).get("mid")
    _del(chat_id, user_msg_id)
    db.set_setting("mini_banner", "" if text.strip() == "-" else text.strip()[:400])
    db.clear_state(admin_id)
    show_admin_banner(chat_id, mid)


def finalize_faq_add(admin_id, chat_id, text, state, user_msg_id):
    mid = (state.get("data") or {}).get("mid")
    _del(chat_id, user_msg_id)
    lines = text.strip().split("\n", 1)
    if len(lines) < 2 or not lines[0].strip() or not lines[1].strip():
        _screen(chat_id, mid, [("e", "cross"), " فرمت درست نیست. خط اول سؤال و از خط دوم جواب را بنویسید."],
                {"inline_keyboard": [[btn("بازگشت", "admin_faq", "back")]]})
        return
    items = _faq_items()
    items.append([lines[0].strip()[:150], lines[1].strip()[:600]])
    db.set_setting("mini_faq", json.dumps(items, ensure_ascii=False))
    db.clear_state(admin_id)
    show_admin_faq(chat_id, mid)


def process_message(message):
    from_user = message.get("from") or {}
    chat_id = message["chat"]["id"]
    user_id = from_user.get("id")
    if user_id is None:
        return

    text = message.get("text")

    if text and (text == "/start" or text.startswith("/start ")):
        db.clear_state(user_id)
        show_main_menu(chat_id, user_id)
        return

    if text == "/cancel":
        db.clear_state(user_id)
        show_main_menu(chat_id, user_id)
        return

    if text == "/admin" and is_admin(user_id):
        db.clear_state(user_id)
        show_admin_panel(chat_id)
        return

    if text == "/tickets" and is_admin(user_id):
        db.clear_state(user_id)
        _send_open_tickets_to_admin(chat_id)
        return

    if text == "/pending" and is_admin(user_id):
        db.clear_state(user_id)
        show_admin_pending(chat_id, None, 0)
        return

    if text and (text == "/order" or text.startswith("/order ")) and is_admin(user_id):
        db.clear_state(user_id)
        arg = normalize_digits(text[len("/order"):]).strip().lstrip("#")
        if not arg.isdigit():
            tg.send_message(chat_id, "شماره سفارش را بعد از دستور بنویسید، مثلاً: /order 125")
            return
        show_admin_order(chat_id, None, int(arg))
        return

    if text and (text == "/user" or text.startswith("/user ")) and is_admin(user_id):
        db.clear_state(user_id)
        arg = text[len("/user"):].strip()
        if not arg:
            db.set_state(user_id, "user_lookup:profile")
            tg.send_message(chat_id, "👤 آیدی عددی کاربر (یا یوزرنیم) را بفرستید.\n\n(/cancel برای انصراف)")
            return
        uid = _resolve_user_lookup(arg)
        if uid is None:
            tg.send_message(chat_id, "کاربر پیدا نشد. آیدی عددی (یا یوزرنیم) را درست بفرستید.")
            return
        show_admin_user_profile(chat_id, uid)
        return

    state = db.get_state(user_id)

    # ادمین: در حال ارسال محتوای پیام همگانی (هر نوع پیامی - متن، عکس، فایل...)
    if is_admin(user_id) and state and state.get("action") == "broadcast_wait":
        handle_broadcast_content_received(chat_id, user_id, message)
        return

    # ادمین: حالت‌های متنی در انتظار ورودی (ضریب جدید، دلیل رد، افزودن بسته، پاسخ تیکت، درصد رفرال)
    if is_admin(user_id) and state and text:
        action = state.get("action") or ""
        if action.startswith("wallet_edit:"):
            finalize_wallet_edit(user_id, chat_id, int(action.split(":", 1)[1]), text, state, message.get("message_id"))
            return
        if action == "discount_new":
            finalize_discount_new(user_id, chat_id, text, state, message.get("message_id"))
            return
        if action.startswith("pause_reason:"):
            finalize_pause_reason(user_id, chat_id, action.split(":", 1)[1], text, state, message.get("message_id"))
            return
        if action == "banner_edit":
            finalize_banner_edit(user_id, chat_id, text, state, message.get("message_id"))
            return
        if action == "faq_add":
            finalize_faq_add(user_id, chat_id, text, state, message.get("message_id"))
            return
        if action == "bonus_cfg":
            finalize_bonus_cfg(user_id, chat_id, text, state, message.get("message_id"))
            return
        if action.startswith("multiplier:"):
            if finalize_set_multiplier(user_id, action.split(":", 1)[1], text):
                db.clear_state(user_id)
            return
        if action == "referral_percent":
            if finalize_set_referral(user_id, text):
                db.clear_state(user_id)
            return
        if action.startswith("reject_reason:"):
            db.clear_state(user_id)
            bits = action.split(":")
            order_id = int(bits[1])
            kind = bits[2] if len(bits) > 2 else None
            finalize_reject(user_id, order_id, text, admin_name_of(from_user), kind=kind)
            return
        if action.startswith("add_package:"):
            db.clear_state(user_id)
            finalize_add_package(user_id, action.split(":", 1)[1], text)
            return
        if action.startswith("ticket_reply:"):
            ticket_id = int(action.split(":", 1)[1])
            finalize_ticket_admin_reply(user_id, ticket_id, text, from_user, message.get("message_id"))
            return
        if action == "support_msg":
            handle_support_message_to_user(user_id, text, state, from_user, message.get("message_id"))
            return
        if action.startswith("user_lookup:"):
            uid = _resolve_user_lookup(text)
            if uid is None:
                tg.send_message(chat_id, "کاربر پیدا نشد. آیدی عددی (یا یوزرنیم) را دوباره بفرستید یا /cancel بزنید.")
                return
            db.clear_state(user_id)
            if action.endswith(":msg"):
                _start_support_msg(user_id, uid)
            else:
                show_admin_user_profile(chat_id, uid)
            return
        if action == "kyc_threshold":
            cleaned = normalize_digits(text.strip()).replace(",", "").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_state(user_id)
            tg.send_message(
                chat_id,
                "✅ احراز هویت برای مبالغ بالا غیرفعال شد." if value <= 0
                else f"✅ حد احراز هویت روی {value:,} تومان تنظیم شد.",
                reply_markup=admin_back_keyboard(),
            )
            return

    if state and state.get("action") == "discount_code" and text:
        handle_discount_text(chat_id, from_user, text, state, message.get("message_id"))
        return

    # هر کاربری: در حال وارد کردن مبلغ شارژ کیف پول
    if state and state.get("action") == "wallet_topup_amount" and text:
        handle_topup_amount_entry(chat_id, from_user, text, state, message.get("message_id"))
        return

    # هر کاربری (شامل ادمین): در حال گفتگوی پشتیبانی است
    if state and state.get("action") == "support_chat" and text:
        ticket_id = (state.get("data") or {}).get("ticket_id")
        handle_ticket_user_message(chat_id, user_id, from_user, ticket_id, text, msg_id=message.get("message_id"))
        return

    # مدیا (عکس/ویدیو/ویس/صوت/فایل): ادمین در حالت پاسخ به تیکت، یا کاربر در حالت نوشتن پیام تیکت
    media_kind, media_file_id = _extract_media(message)
    if media_kind:
        action = (state or {}).get("action") or ""
        if is_admin(user_id) and (action.startswith("ticket_reply:") or action == "support_msg"):
            handle_ticket_admin_media(user_id, message, state, from_user)
            return
        if action == "support_chat":
            caption = (message.get("caption") or "").strip()
            ticket_id = (state.get("data") or {}).get("ticket_id")
            handle_ticket_user_message(chat_id, user_id, from_user, ticket_id, caption,
                                       msg_id=message.get("message_id"), media=(media_kind, media_file_id, caption))
            return

    if message.get("photo"):
        handle_receipt_photo(chat_id, from_user, message)
        return

    if text:
        handled = handle_username_text(chat_id, from_user, text, message.get("message_id"))
        if handled:
            return

    if media_kind and (db.get_latest_user_order_in_status(user_id, "awaiting_payment")
                       or db.get_latest_user_order_in_status(user_id, "awaiting_kyc")):
        tg.send_message(chat_id, "📷 رسید و مدارک احراز هویت را باید به‌صورت «عکس» بفرستید (نه فایل/ویدیو/ویس).")
        return

    tg.send_message(chat_id, "برای شروع /start را بفرستید.")


# دکمه‌هایی که فقط ادمین حق زدنشان را دارد
_ADMIN_ONLY_PREFIXES = ("admin_", "approve:", "reject:", "complete:", "broadcast_", "menu:admin")
# دکمه‌هایی که هر حالت گفتگوی نیمه‌کاره (مبلغ شارژ، تیکت، ...) را پاک می‌کنند
_STATE_CLEARING_PREFIXES = ("menu:", "back:main", "admin_sales", "admin_packages:")


def process_callback_query(callback_query):
    from_user = callback_query["from"]
    user_id = from_user["id"]
    cq_id = callback_query["id"]
    message = callback_query.get("message") or {}
    if not message:
        tg.answer_callback_query(cq_id)
        return
    chat_id = message["chat"]["id"]
    message_id = message["message_id"]
    data = callback_query.get("data", "")

    # ضد اسپم کلیک
    if not db.check_and_update_rate_limit(user_id, config.CALLBACK_RATE_LIMIT_SECONDS):
        tg.answer_callback_query(cq_id)
        return

    if data.startswith(_ADMIN_ONLY_PREFIXES) and not is_admin(user_id):
        tg.answer_callback_query(cq_id, "دسترسی ندارید.", show_alert=True)
        return

    if data.startswith(_STATE_CLEARING_PREFIXES):
        db.clear_state(user_id)

    # ------------------------- (جدید) تخفیف / توقف فروش / خرید مجدد / ابزار ادمین -------------------------
    if data.startswith("disc:"):
        oid = int(data.split(":", 1)[1])
        order = db.get_order(oid)
        if (not order or order["user_id"] != user_id or order["status"] != "awaiting_payment"
                or order["product_type"] == "wallet_topup" or _order_is_stale(order)):
            tg.answer_callback_query(cq_id, "این سفارش دیگر قابل ویرایش نیست.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        db.set_state(user_id, "discount_code", {"order_id": oid, "mid": message_id})
        _screen(chat_id, message_id, [("e", "invite"), " ", ("b", "کد تخفیف"), "\n\nکد تخفیف خود را بفرستید."],
                {"inline_keyboard": [[btn("بازگشت", f"disc_back:{oid}", "back")]]})
        return

    if data.startswith("disc_back:") or data.startswith("disc_remove:"):
        oid = int(data.split(":", 1)[1])
        db.clear_state(user_id)
        if data.startswith("disc_remove:"):
            db.remove_discount(oid, user_id)
        order = db.get_order(oid)
        tg.answer_callback_query(cq_id, "کد تخفیف حذف شد." if data.startswith("disc_remove:") else None)
        if order and order["user_id"] == user_id and order["status"] == "awaiting_payment":
            show_order_payment(chat_id, message_id, order)
        else:
            show_main_menu(chat_id, user_id, edit=message_id)
        return

    if data.startswith("notify:"):
        product = data.split(":", 1)[1]
        if product not in PRODUCT_FA:
            tg.answer_callback_query(cq_id)
            return
        if not db.get_pause(product):
            tg.answer_callback_query(cq_id, "فروش همین الان فعال است.", show_alert=True)
            return
        db.add_notify(user_id, product)
        tg.answer_callback_query(cq_id, "✅ ثبت شد؛ وقتی فروش شروع شد خبرتان می‌کنیم.", show_alert=True)
        return

    if data.startswith("reorder:"):
        o = db.get_order(int(data.split(":", 1)[1]))
        if not o or o["user_id"] != user_id or o["product_type"] == "wallet_topup":
            tg.answer_callback_query(cq_id, "سفارش پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        create_order_and_show_payment(chat_id, from_user, o["product_type"], o["package_key"], edit=message_id)
        return

    if data.startswith("use_uname:"):
        order = db.get_order(int(data.split(":", 1)[1]))
        last = db.get_last_target_username(user_id)
        if not order or order["user_id"] != user_id or order["status"] != "awaiting_username" or not last:
            tg.answer_callback_query(cq_id, "این مرحله دیگر فعال نیست.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _finalize_username(chat_id, order, last, message_id, via_text=False)
        return

    if data == "admin_pending" or data.startswith("admin_pending:"):
        tg.answer_callback_query(cq_id)
        page = int(data.split(":", 1)[1]) if ":" in data else 0
        show_admin_pending(chat_id, message_id, page)
        return

    if data.startswith("admin_order:"):
        tg.answer_callback_query(cq_id)
        show_admin_order(chat_id, message_id, int(data.split(":", 1)[1]))
        return

    if data.startswith("admin_receipt:"):
        o = db.get_order(int(data.split(":", 1)[1]))
        tg.answer_callback_query(cq_id)
        if o and o.get("receipt_file_id"):
            if o.get("kyc_required"):
                _send_kyc_files_to_admin(user_id, o)
            tg.send_photo_by_file_id(user_id, o["receipt_file_id"], caption=f"رسید سفارش #{o['id']}")
        return

    if data.startswith("admin_cancelorder:"):
        oid = int(data.split(":", 1)[1])
        tg.answer_callback_query(cq_id)
        _screen(chat_id, message_id,
                [("e", "warning"), f" سفارش #{oid} لغو شود؟\n\n",
                 "اگر با کیف پول پرداخت شده، مبلغ خودکار برمی‌گردد و به کاربر خبر داده می‌شود. "
                 "اگر کارت‌به‌کارت پرداخت کرده، بازپرداخت را خودتان (مثلاً با ویرایش موجودی) انجام دهید."],
                {"inline_keyboard": [[btn("بله، لغو شود", f"admin_cancelyes:{oid}", "cross")],
                                     [btn("بازگشت", f"admin_order:{oid}", "back")]]})
        return

    if data.startswith("admin_cancelyes:"):
        oid = int(data.split(":", 1)[1])
        ok = do_admin_cancel(user_id, admin_name_of(from_user), oid)
        tg.answer_callback_query(cq_id, "سفارش لغو شد." if ok else "این سفارش دیگر قابل لغو نیست.", show_alert=not ok)
        show_admin_order(chat_id, message_id, oid)
        return

    if data.startswith("admin_walletedit:"):
        tg.answer_callback_query(cq_id)
        start_wallet_edit(user_id, chat_id, int(data.split(":", 1)[1]), message_id)
        return

    if data == "admin_disc_menu":
        db.clear_state(user_id)
        tg.answer_callback_query(cq_id)
        show_admin_discounts(chat_id, message_id)
        return

    if data == "admin_disc_new":
        tg.answer_callback_query(cq_id)
        db.set_state(user_id, "discount_new", {"mid": message_id})
        _screen(chat_id, message_id, [("e", "plus"), " ", ("b", "کد تخفیف جدید"), "\n\nمشخصات را در یک پیام بفرستید.\n\n", DISC_HELP],
                {"inline_keyboard": [[btn("بازگشت", "admin_disc_menu", "back")]]})
        return

    if data.startswith("admin_disc_toggle:") or data.startswith("admin_disc_del:"):
        code = data.split(":", 1)[1]
        if data.startswith("admin_disc_toggle:"):
            db.toggle_discount(code)
        else:
            db.delete_discount(code)
        tg.answer_callback_query(cq_id, "انجام شد.")
        show_admin_discounts(chat_id, message_id)
        return

    if data.startswith("admin_pause:"):
        product = data.split(":", 1)[1]
        if product not in PRODUCT_FA:
            tg.answer_callback_query(cq_id)
            return
        if db.get_pause(product):
            db.set_pause(product, "")
            tg.answer_callback_query(cq_id, f"فروش {PRODUCT_FA[product]} فعال شد ✅")
            _notify_resume_job(product)
            show_admin_sales(chat_id, message_id)
            return
        tg.answer_callback_query(cq_id)
        db.set_state(user_id, f"pause_reason:{product}", {"mid": message_id})
        _screen(chat_id, message_id,
                [("e", "lock"), " ", ("b", f"توقف فروش {PRODUCT_FA[product]}"),
                 "\n\nپیامی که کاربران هنگام ورود به این بخش می‌بینند (دلیل توقف) را بنویسید.\n"
                 "برای متن پیش‌فرض فقط «-» بفرستید."],
                {"inline_keyboard": [[btn("انصراف", "admin_sales", "back")]]})
        return

    if data == "admin_bonus_start":
        tg.answer_callback_query(cq_id)
        thr, amt = db.get_topup_bonus()
        cur = f"فعال: شارژ حداقل {thr:,} ← {amt:,} تومان هدیه" if thr > 0 and amt > 0 else "غیرفعال"
        db.set_state(user_id, "bonus_cfg", {"mid": message_id})
        _screen(chat_id, message_id,
                [("e", "invite"), " ", ("b", "هدیه‌ی شارژ کیف پول"), f"\n\nوضعیت فعلی: {cur}\n\n",
                 "دو عدد بفرستید: حداقل مبلغ شارژ و مبلغ هدیه (تومان).\nمثال: 1000000 50000\n(برای غیرفعال‌کردن: 0)\n\n",
                 "هدیه به موجودی کیف پول اضافه می‌شود و برای هر خریدی قابل استفاده است."],
                {"inline_keyboard": [[btn("پنل مدیریت", "menu:admin", "admin")]]})
        return

    if data == "admin_broadcast_start":
        tg.answer_callback_query(cq_id)
        rows = [[btn(f"{AUD_LABELS[a]} ({len(db.list_audience_ids(a)):,})", f"admin_bc_aud:{a}", "users")]
                for a in ("all", "premium", "stars", "never")]
        rows.append([btn("پنل مدیریت", "menu:admin", "admin")])
        _screen(chat_id, message_id, [("e", "broadcast"), " ", ("b", "پیام همگانی"), "\n\nمخاطب را انتخاب کنید:"],
                {"inline_keyboard": rows})
        return

    if data.startswith("admin_bc_aud:"):
        aud = data.split(":", 1)[1]
        if aud not in AUD_LABELS:
            tg.answer_callback_query(cq_id)
            return
        tg.answer_callback_query(cq_id)
        db.set_state(user_id, "broadcast_wait", {"aud": aud})
        _screen(chat_id, message_id,
                [("e", "broadcast"), f" مخاطب: {AUD_LABELS[aud]} ({len(db.list_audience_ids(aud)):,} نفر)\n\n",
                 "پیامی که می‌خواهید ارسال شود را همین‌جا بفرستید (متن، عکس، فایل یا هر نوع پیام دیگر؛ عیناً ارسال می‌شود).\n\n"
                 "برای انصراف «پنل مدیریت» را بزنید."],
                {"inline_keyboard": [[btn("پنل مدیریت", "menu:admin", "admin")]]})
        return

    if data == "broadcast_send":
        state = db.get_state(user_id)
        if not state or state.get("action") != "broadcast_confirm":
            tg.answer_callback_query(cq_id, "این درخواست منقضی شده یا قبلاً انجام شده.", show_alert=True)
            return
        sdata = state.get("data") or {}
        db.clear_state(user_id)
        tg.answer_callback_query(cq_id, "شروع ارسال...")
        _screen(chat_id, message_id, ["⏳ در حال ارسال پیام همگانی... (بسته به تعداد کاربران ممکن است کمی طول بکشد)"], None)
        _broadcast_job(user_id, chat_id, sdata.get("message_id"), sdata.get("aud", "all"))
        return

    if data == "admin_banner":
        db.clear_state(user_id)
        tg.answer_callback_query(cq_id)
        show_admin_banner(chat_id, message_id)
        return

    if data == "admin_banner_edit":
        tg.answer_callback_query(cq_id)
        db.set_state(user_id, "banner_edit", {"mid": message_id})
        _screen(chat_id, message_id, ["متن بنر را بفرستید (حداکثر ۴۰۰ کاراکتر). برای حذف بنر فقط «-» بفرستید."],
                {"inline_keyboard": [[btn("بازگشت", "admin_banner", "back")]]})
        return

    if data == "admin_banner_clear":
        db.set_setting("mini_banner", "")
        tg.answer_callback_query(cq_id, "بنر حذف شد.")
        show_admin_banner(chat_id, message_id)
        return

    if data == "admin_faq":
        db.clear_state(user_id)
        tg.answer_callback_query(cq_id)
        show_admin_faq(chat_id, message_id)
        return

    if data == "admin_faq_add":
        tg.answer_callback_query(cq_id)
        db.set_state(user_id, "faq_add", {"mid": message_id})
        _screen(chat_id, message_id, ["سؤال و جواب را در یک پیام بفرستید:\nخط اول = سؤال\nاز خط دوم به بعد = جواب"],
                {"inline_keyboard": [[btn("بازگشت", "admin_faq", "back")]]})
        return

    if data.startswith("admin_faq_del:"):
        items = _faq_items()
        i = int(data.split(":", 1)[1])
        if 0 <= i < len(items):
            items.pop(i)
            db.set_setting("mini_faq", json.dumps(items, ensure_ascii=False))
        tg.answer_callback_query(cq_id, "حذف شد.")
        show_admin_faq(chat_id, message_id)
        return

    if data == "admin_faq_reset":
        db.set_setting("mini_faq", "")
        tg.answer_callback_query(cq_id, "به پیش‌فرض برگشت.")
        show_admin_faq(chat_id, message_id)
        return

    # ------------------------- کاربر: ناوبری اصلی -------------------------
    if data == "check_membership":
        if is_admin(user_id) or is_member_of_channel(user_id):
            tg.answer_callback_query(cq_id, "✅ عضویت شما تایید شد.")
            show_main_menu(chat_id, user_id)
        else:
            tg.answer_callback_query(cq_id, "❌ شما هنوز عضو کانال نشده‌اید!", show_alert=True)
            send_force_join_message(chat_id)
        return

    if data == "back:main":
        tg.answer_callback_query(cq_id)
        show_main_menu(chat_id, user_id, edit=message_id)
        return

    if data == "menu:shop":
        tg.answer_callback_query(cq_id)
        show_shop_menu(chat_id, edit=message_id)
        return

    if data == "menu:premium":
        tg.answer_callback_query(cq_id)
        show_premium_menu(chat_id, edit=message_id)
        return

    if data == "menu:stars":
        tg.answer_callback_query(cq_id)
        show_stars_menu(chat_id, edit=message_id)
        return

    if data == "menu:wallet":
        tg.answer_callback_query(cq_id)
        show_wallet_menu(chat_id, user_id, edit=message_id)
        return

    if data == "wallet:history":
        tg.answer_callback_query(cq_id)
        show_wallet_history(chat_id, user_id, edit=message_id)
        return

    if data == "wallet:topup":
        start_wallet_topup(chat_id, user_id, message_id, cq_id)
        return

    if data == "menu:referral":
        tg.answer_callback_query(cq_id)
        show_referral_info(chat_id, user_id, edit=message_id)
        return

    if data == "menu:contact":
        tg.answer_callback_query(cq_id)
        show_contact_us(chat_id, edit=message_id)
        return

    if data == "menu:support":
        tg.answer_callback_query(cq_id)
        ticket = db.get_open_ticket_for_user(user_id)
        ticket_id = ticket["id"] if ticket else db.create_ticket(user_id)
        db.set_state(user_id, "support_chat", {"ticket_id": ticket_id})
        _screen(chat_id, message_id,
                [("e", "contact"), " ", ("b", "پشتیبانی"), "\n\n",
                 "✍️ همین الان ", ("b", "پیام یا مشکلتان را بنویسید و بفرستید"),
                 " (می‌توانید چند پیام بفرستید؛ عکس، ویدیو، ویس و فایل هم قبول است).\n\n",
                 "⏳ سپس منتظر پاسخ پشتیبانی بمانید؛ جواب همین‌جا برایتان می‌آید.\n\n",
                 "✅ بعد از پاسخ، اگر مشکلتان حل شد تیکت را ببندید، و اگر نه گفتگو را ادامه دهید.\n\n",
                 f"🕐 تیکتی که بعد از پاسخ پشتیبانی {_ticket_auto_close_label()} بی‌پاسخ بماند، خودکار بسته می‌شود."],
                {"inline_keyboard": [nav_row("menu:contact")]})
        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"] != user_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)
        db.set_state(user_id, "support_chat", {"ticket_id": ticket_id})
        tg.send_message(chat_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"] != user_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)
        ustate = db.get_state(user_id)
        if ustate and ustate.get("action") == "support_chat":
            db.clear_state(user_id)
        tg.answer_callback_query(cq_id, "گفتگو پایان یافت.")
        tg.edit_message_reply_markup(chat_id, message_id)  # دکمه‌های همان پاسخ برداشته می‌شود
        tg.send_message(chat_id, "🔒 تیکت بسته شد. خوشحالیم که مشکلتان حل شد؛ متشکریم! 🙏")
        return

    if data.startswith("kyc_confirm:"):
        handle_kyc_confirm(callback_query, int(data.split(":", 1)[1]))
        return

    if data.startswith("myorder:"):
        tg.answer_callback_query(cq_id)
        show_order_detail(chat_id, user_id, int(data.split(":", 1)[1]), edit=message_id)
        return

    if data == "menu:myorders":
        tg.answer_callback_query(cq_id)
        show_my_orders(chat_id, user_id, edit=message_id)
        return

    # ------------------------- کاربر: سفارش و پرداخت -------------------------
    if data.startswith("select:"):
        _, product_type, package_key = data.split(":", 2)
        tg.answer_callback_query(cq_id)
        create_order_and_show_payment(chat_id, from_user, product_type, package_key, edit=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"] != user_id or order["status"] != "awaiting_payment":
            tg.answer_callback_query(cq_id, "این سفارش دیگر قابل پرداخت نیست.", show_alert=True)
            return
        if _order_is_stale(order):
            db.expire_order_atomic(order_id)
            tg.answer_callback_query(cq_id)
            if order["product_type"] == "wallet_topup":
                _screen(chat_id, message_id, [("e", "clock"), " مهلت واریز این سفارش شارژ تمام شده است. دوباره شارژ بزنید."],
                        {"inline_keyboard": [[btn("شارژ کیف پول", "wallet:topup", "wallet")], nav_row("menu:wallet")]})
            else:
                _screen(chat_id, message_id,
                        [("e", "clock"), f" اعتبار قیمت سفارش #{order_id} تمام شده است. با دکمه‌ی زیر قیمت تازه بگیرید:"],
                        expired_price_keyboard(order["product_type"], order["package_key"]))
            return
        if order["product_type"] != "wallet_topup" and db.get_pause(order["product_type"]):
            tg.answer_callback_query(cq_id, "فروش این بخش موقتاً متوقف است.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        show_card_payment(chat_id, message_id, order)
        return

    if data.startswith("pay_wallet:"):
        handle_pay_wallet(chat_id, message_id, user_id, cq_id, int(data.split(":", 1)[1]))
        return

    if data.startswith("refresh_order:"):
        order_id = int(data.split(":", 1)[1])
        order = db.get_order(order_id)
        if not order or order["user_id"] != user_id or order["product_type"] == "wallet_topup":
            tg.answer_callback_query(cq_id, "سفارش پیدا نشد.", show_alert=True)
            return
        if order["status"] != "awaiting_payment":
            tg.answer_callback_query(cq_id, "این سفارش دیگر قابل به‌روزرسانی نیست.", show_alert=True)
            return
        carry = order.get("discount_code")
        db.expire_order_atomic(order_id)
        tg.answer_callback_query(cq_id, "در حال دریافت قیمت جدید...")
        create_order_and_show_payment(chat_id, from_user, order["product_type"], order["package_key"],
                                      edit=message_id, carry_code=carry)
        return

    if data.startswith("cancel_order:"):
        order_id = int(data.split(":", 1)[1])
        order = db.get_order(order_id)
        ok = db.cancel_order_atomic(order_id, user_id)
        if ok:
            refunded = db.refund_wallet_order(order_id)  # اگر قبلاً با کیف پول پرداخت شده بود
            tg.answer_callback_query(cq_id, "سفارش لغو شد.")
            text = f"سفارش #{order_id} لغو شد."
            if refunded:
                text += f"\n💰 مبلغ {refunded:,} تومان به کیف پول شما برگشت."
            back = "menu:wallet" if (order and order["product_type"] == "wallet_topup") else "menu:shop"
            _screen(chat_id, message_id, [text], {"inline_keyboard": [nav_row(back)]})
        else:
            tg.answer_callback_query(cq_id, "این سفارش دیگر قابل لغو نیست.", show_alert=True)
        return

    # ------------------------- ادمین: تصمیم روی سفارش -------------------------
    if data.startswith("approve:"):
        handle_approve(callback_query, int(data.split(":", 1)[1]))
        return

    if data.startswith("complete:"):
        handle_complete(callback_query, int(data.split(":", 1)[1]))
        return

    if data.startswith("reject:"):
        bits = data.split(":")
        handle_reject_start(callback_query, int(bits[1]), bits[2] if len(bits) > 2 else None)
        return

    # ------------------------- ادمین: پنل -------------------------
    if data == "menu:admin":
        tg.answer_callback_query(cq_id)
        show_admin_panel(chat_id, edit=message_id)
        return

    if data == "admin_sales":
        tg.answer_callback_query(cq_id)
        show_admin_sales(chat_id, edit=message_id)
        return

    if data == "admin_pending":
        tg.answer_callback_query(cq_id)
        pending = db.list_pending_orders()
        processing = db.list_processing_orders()
        kyc_wait = db.list_awaiting_kyc_orders()
        if not pending and not processing and not kyc_wait:
            tg.send_message(chat_id, "سفارش در انتظاری وجود ندارد.", reply_markup=admin_back_keyboard())
            return
        lines = []
        for o in pending:
            who = "شارژ کیف پول" if o["product_type"] == "wallet_topup" else f"@{o['target_username']} — {o['package_label']}"
            lines.append(f"#{o['id']} — {who} — {o['price_toman']:,} تومان")
        text_out = ""
        if lines:
            text_out += "سفارش‌های در انتظار تایید پرداخت:\n\n" + "\n".join(lines)
        if kyc_wait:
            klines = [f"#{o['id']} — {'شارژ کیف پول' if o['product_type'] == 'wallet_topup' else o['package_label']} — "
                      f"{o['price_toman']:,} تومان — کاربر {o['user_id']} ({len(db.get_kyc_files(o['id']))} عکس)"
                      for o in kyc_wait]
            text_out += (("\n\n" if text_out else "") +
                         "رسید رسیده ولی منتظر مدارک احراز هویت از کاربر:\n\n" + "\n".join(klines))
        if processing:
            plines = [f"#{o['id']} — @{o['target_username']} — {o['package_label']} — "
                      f"{o['price_toman']:,} تومان — رسیدگی: {o.get('decided_by_name') or '-'}" for o in processing]
            text_out += ("\n\n" if text_out else "") + "تایید‌شده و در حال انجام (منتظر «تکمیل سفارش»):\n\n" + "\n".join(plines)
        tg.send_message(chat_id, text_out +
                        "\n\n(برای تصمیم، روی پیام اصلی هر سفارش که برایتان ارسال شده دکمه‌ها را بزنید.)",
                        reply_markup=admin_back_keyboard())
        return

    if data.startswith("admin_packages:"):
        product_type = data.split(":", 1)[1]
        tg.answer_callback_query(cq_id)
        title = "بسته‌های پرمیوم" if product_type == "premium" else "بسته‌های استارز"
        tg.edit_message_text(chat_id, message_id, f"مدیریت {title}:\nبرای حذف، روی بسته بزنید.",
                             reply_markup=admin_packages_keyboard(product_type))
        return

    if data.startswith("admin_addpkg:"):
        product_type = data.split(":", 1)[1]
        db.set_state(user_id, f"add_package:{product_type}")
        tg.answer_callback_query(cq_id)
        if product_type == "stars":
            msg = (f"تعداد استارز دلخواه را بفرستید (فقط عدد، حداقل {config.STARS_MIN_AMOUNT}).\n"
                   f"قیمت پایه به‌صورت خودکار {config.STARS_USD_PER_STAR}$ × تعداد محاسبه می‌شود.\nمثال: 300")
        else:
            msg = "تعداد ماه و قیمت پایه (دلار) را با فاصله بفرستید.\nمثال: 9 25"
        tg.send_message(chat_id, msg,
                        reply_markup={"inline_keyboard": [[btn("بازگشت", f"admin_packages:{product_type}", "back")]]})
        return

    if data.startswith("admin_delpkg_ask:"):
        pkg_id = int(data.split(":", 1)[1])
        pkg = db.get_package_by_id(pkg_id)
        if not pkg:
            tg.answer_callback_query(cq_id, "این بسته پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        tg.edit_message_text(chat_id, message_id, f"آیا از حذف «{pkg['label']}» مطمئنید؟",
                             reply_markup=admin_delpkg_confirm_keyboard(pkg_id, pkg["product_type"]))
        return

    if data.startswith("admin_delpkg_do:"):
        _, pkg_id_str, product_type = data.split(":", 2)
        db.delete_package(int(pkg_id_str))
        tg.answer_callback_query(cq_id, "حذف شد.")
        tg.edit_message_text(chat_id, message_id, "✅ حذف شد.\n\nمدیریت بسته‌ها:",
                             reply_markup=admin_packages_keyboard(product_type))
        return

    if data == "admin_usercount":
        tg.answer_callback_query(cq_id)
        total = db.count_users()
        parts = [("e", "users"), " تعداد کل کاربرانی که تاکنون /start زده‌اند: ", ("b", f"{total:,}"), " نفر"]
        text, entities = build_message(parts)
        tg.send_message(chat_id, text, entities=entities, reply_markup=admin_back_keyboard())
        return

    if data == "admin_stats":
        tg.answer_callback_query(cq_id)
        _send_sales_report(chat_id)
        return

    if data == "admin_setreferral_start":
        db.set_state(user_id, "referral_percent")
        tg.answer_callback_query(cq_id)
        current = db.get_referral_percent()
        tg.send_message(
            chat_id,
            f"درصد فعلی پورسانت رفرال: {current:g}٪\n\n"
            f"درصد جدید را بفرستید (عدد بین ۰ تا ۱۰۰؛ برای مثال 1 یعنی ۱٪). با ارسال 0 سیستم رفرال غیرفعال می‌شود.\n"
            f"این مبلغ به‌صورت اعتبار به کیف پول دعوت‌کننده اضافه می‌شود و در خرید پرمیوم/استارز قابل استفاده است.\n\n"
            f"(برای انصراف، /cancel را بزنید)",
            reply_markup=admin_back_keyboard(),
        )
        return

    if data == "admin_broadcast_start":
        db.set_state(user_id, "broadcast_wait", {})
        tg.answer_callback_query(cq_id)
        tg.send_message(
            chat_id,
            "📢 پیامی که می‌خواهید برای همه‌ی کاربرانی که با ربات شروع کرده‌اند ارسال شود را همین‌جا بفرستید.\n"
            "می‌تواند متن، عکس، فایل یا هر نوع پیام دیگری باشد؛ دقیقاً همان چیزی که بفرستید عیناً برای همه ارسال می‌شود.\n\n"
            "برای انصراف، «پنل مدیریت» را بزنید.",
            reply_markup=admin_back_keyboard(),
        )
        return

    if data == "broadcast_send":
        state = db.get_state(user_id)
        if not state or state.get("action") != "broadcast_confirm":
            tg.answer_callback_query(cq_id, "این درخواست منقضی شده یا قبلاً انجام شده.", show_alert=True)
            return
        message_id_to_send = (state.get("data") or {}).get("message_id")
        db.clear_state(user_id)
        tg.answer_callback_query(cq_id, "شروع ارسال...")
        tg.send_message(chat_id, "⏳ در حال ارسال پیام همگانی... (بسته به تعداد کاربران ممکن است کمی طول بکشد)")
        success, fail = _run_broadcast(user_id, message_id_to_send)
        tg.send_message(chat_id, f"📢 پیام همگانی تمام شد.\n✅ ارسال موفق: {success}\n❌ ناموفق: {fail}",
                        reply_markup=admin_back_keyboard())
        return

    if data == "broadcast_cancel":
        db.clear_state(user_id)
        tg.answer_callback_query(cq_id, "لغو شد.")
        tg.send_message(chat_id, "پیام همگانی لغو شد.", reply_markup=admin_back_keyboard())
        return

    if data == "admin_tickets":
        tg.answer_callback_query(cq_id)
        _send_open_tickets_to_admin(chat_id)
        return

    if data.startswith("admin_ticket_reply:"):
        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_state(user_id, f"ticket_reply:{ticket_id}")
        tg.answer_callback_query(cq_id)
        tg.send_message(
            chat_id,
            f"✍️ پاسخ خود را برای تیکت #{ticket_id} بفرستید؛ متن، عکس، ویدیو، ویس، صوت یا فایل (با کپشن) "
            f"هر کدام مستقیم برای کاربر می‌رود.\n(برای خروج از حالت پاسخ، /cancel را بزنید)",
        )
        return

    if data.startswith("admin_ticket_close:"):
        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, user_id, from_user, "این تیکت را بست", with_buttons=False)
        ustate = db.get_state(ticket["user_id"])
        if ustate and ustate.get("action") == "support_chat" and (ustate.get("data") or {}).get("ticket_id") == ticket_id:
            db.clear_state(ticket["user_id"])
        tg.answer_callback_query(cq_id, "تیکت بسته شد.")
        tg.edit_message_reply_markup(chat_id, message_id)  # دکمه‌ها برداشته می‌شود؛ پیام جدا لازم نیست
        if was_open:
            try:
                tg.send_message(ticket["user_id"],
                                "🔒 این گفتگوی پشتیبانی بسته شد. در صورت نیاز، دوباره از «ارتباط با ما» تیکت جدید باز کنید.")
            except Exception:
                logger.exception("خطا در اطلاع بسته‌شدن تیکت به کاربر")
        return

    if data.startswith("admin_msguser:"):
        try:
            target_uid = int(data.split(":", 1)[1])
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        if not db.get_user(target_uid):
            tg.answer_callback_query(cq_id, "کاربر پیدا نشد.", show_alert=True)
            return
        tg.answer_callback_query(cq_id)
        _start_support_msg(user_id, target_uid)
        return

    if data.startswith("admin_profile:"):
        try:
            target_uid = int(data.split(":", 1)[1])
        except ValueError:
            tg.answer_callback_query(cq_id)
            return
        tg.answer_callback_query(cq_id)
        show_admin_user_profile(chat_id, target_uid)  # پیام جدید (چون پیام اصلی ممکن است عکس رسید باشد)
        return

    if data == "admin_userlookup_start":
        db.set_state(user_id, "user_lookup:profile")
        tg.answer_callback_query(cq_id)
        tg.send_message(chat_id, "👤 آیدی عددی کاربر (یا یوزرنیم) را بفرستید.\n\n(/cancel برای انصراف)",
                        reply_markup=admin_back_keyboard())
        return

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

    if data.startswith("admin_setmult:"):
        start_set_multiplier(callback_query, data.split(":", 1)[1])
        return

    tg.answer_callback_query(cq_id)


def _check_expired_awaiting_orders():
    """سفارش‌های در انتظار پرداختی که مهلتشان گذشته و هنوز رسیدی نفرستاده‌اند را منقضی می‌کند و به کاربر
    پیام انقضا می‌دهد (خرید: با دکمه‌ی قیمت جدید؛ شارژ کیف پول: با دکمه‌ی شارژ مجدد)."""
    now = int(time.time())
    try:
        stale_orders = db.list_stale_awaiting_orders(
            now - config.PRICE_VALIDITY_SECONDS, now - config.TOPUP_VALIDITY_SECONDS
        )
    except Exception:
        logger.exception("خطا در بررسی سفارش‌های منقضی")
        return
    for o in stale_orders:
        if not db.expire_order_atomic(o["id"]):
            continue  # یکی دیگر (مثلاً همزمان با ارسال رسید) زودتر وضعیتش را عوض کرده بود
        try:
            if o["product_type"] == "wallet_topup":
                tg.send_message(
                    o["user_id"],
                    f"⏰ درخواست شارژ کیف پول #{o['id']} منقضی شد (رسیدی دریافت نشد).\n"
                    "اگر واریز کرده‌اید با پشتیبانی تماس بگیرید؛ در غیر این صورت می‌توانید دوباره شارژ بزنید.",
                    reply_markup={"inline_keyboard": [[btn("شارژ کیف پول", "wallet:topup", "wallet")],
                                                      nav_row("menu:wallet")]},
                )
                continue
            validity_min = config.PRICE_VALIDITY_SECONDS // 60
            tg.send_message(
                o["user_id"],
                f"⏰ سفارش #{o['id']} ({o['package_label']}) منقضی شد.\n\n"
                f"بیش از {validity_min} دقیقه از ثبت سفارش گذشته و قیمت لحظه‌ای تتر ممکن است تغییر کرده باشد.\n"
                f"⚠️ این قیمت دیگر معتبر نیست؛ در صورت واریز، رسید شما تایید نخواهد شد.\n\n"
                f"لطفاً با دکمه‌ی زیر قیمت تازه بگیرید:",
                reply_markup=expired_price_keyboard(o["product_type"], o["package_key"]),
            )
        except Exception:
            logger.exception("خطا در ارسال پیام انقضا برای سفارش #%s", o["id"])


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


_maintenance_state = {"last_run": 0.0}


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


# ---------------------------------------------------------------------------
# مسیرهای Flask
# ---------------------------------------------------------------------------

def _setup_key_ok():
    return hmac.compare_digest(request.args.get("key", ""), config.SETUP_KEY)


def _full_name(u):
    return ((u.get("first_name") or "") + " " + (u.get("last_name") or "")).strip()


@app.route(config.WEBHOOK_PATH, methods=["POST"])
def webhook():
    incoming_secret = request.headers.get("X-Telegram-Bot-Api-Secret-Token", "")
    if not hmac.compare_digest(incoming_secret, config.WEBHOOK_SECRET):
        return jsonify({"ok": False, "error": "forbidden"}), 403

    update = request.get_json(silent=True) or {}
    try:
        _run_maintenance()

        if "message" in update:
            message = update["message"]
            from_user = message.get("from") or {}
            if from_user.get("is_bot") or (message.get("chat") or {}).get("type") != "private":
                return jsonify({"ok": True})
            user_id = from_user.get("id")
            chat_id = message["chat"]["id"]
            if user_id is None:
                return jsonify({"ok": True})

            # ثبت/به‌روزرسانی کاربر (برای پیام همگانی) — حتی قبل از چک عضویت
            db.upsert_user(user_id, from_user.get("username"), _full_name(from_user))

            # ثبت معرف (لینک رفرال) — قبل از چک عضویت تا با عضو نبودن از دست نرود
            maybe_register_referral(message)

            # ادمین در حال ارسال محتوای پیام همگانی: هر نوع پیامی پذیرفته می‌شود (ادمین از عضویت اجباری معاف است)
            if not is_admin(user_id) and not is_member_of_channel(user_id):
                send_force_join_message(chat_id)
                return jsonify({"ok": True})

            process_message(message)

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

            if not from_user.get("is_bot"):
                db.upsert_user(user_id, from_user.get("username"), _full_name(from_user))

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

            process_callback_query(cq)
    except Exception:
        logger.exception("خطا در پردازش آپدیت")
    return jsonify({"ok": True})


@app.route("/set_webhook", methods=["GET"])
def route_set_webhook():
    if not _setup_key_ok():
        return jsonify({"ok": False, "error": "forbidden"}), 403
    url = config.BASE_URL.rstrip("/") + config.WEBHOOK_PATH
    result = tg.set_webhook(url, secret_token=config.WEBHOOK_SECRET)
    return jsonify(result)


@app.route("/set_commands", methods=["GET"])
def route_set_commands():
    if not _setup_key_ok():
        return jsonify({"ok": False, "error": "forbidden"}), 403
    # طبق درخواست: هیچ دستوری جز «شروع مجدد» در منوی دستورات تلگرام نمایش داده نشود.
    commands = [{"command": "start", "description": "شروع مجدد"}]
    default_result = tg.set_my_commands(commands)
    admin_results = {}
    for admin_id in config.ADMIN_IDS:
        admin_results[str(admin_id)] = tg.set_my_commands(commands, chat_id=admin_id)
    return jsonify({"default": default_result, "admin": admin_results})


@app.route("/set_menu_button", methods=["GET"])
def route_set_menu_button():
    if not _setup_key_ok():
        return jsonify({"ok": False, "error": "forbidden"}), 403
    return jsonify(tg._post("setChatMenuButton", menu_button=json.dumps({
        "type": "web_app", "text": "مینی‌اپ", "web_app": {"url": config.BASE_URL.rstrip("/") + "/app"}})))


@app.route("/", methods=["GET"])
def index():
    # کرون هر چند دقیقه این آدرس را باز می‌کند تا کارهای دوره‌ای
    # (منقضی‌کردن سفارش‌های کهنه، بستن خودکار تیکت‌ها) هم بدون پیام کاربر اجرا شوند
    try:
        _run_maintenance()
    except Exception:
        logger.exception("خطا در کارهای دوره‌ای (index)")
    return jsonify({"ok": True, "service": "premium-stars-bot"})


# مینی‌اپ (صفحات و API در miniapp.py)
import miniapp  # noqa: E402
miniapp.register(app)


if __name__ == "__main__":
    app.run(port=5000, debug=True)
