import os
import json
import time
import uuid
import logging
import re
import zipfile
import sqlite3
import threading
from datetime import datetime

import telebot
from telebot import types

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "data")
LOG_DIR = os.path.join(BASE_DIR, "log")
BACKUP_DIR = os.path.join(BASE_DIR, "backups")

ENV_FILE = os.path.join(BASE_DIR, ".env")
CONFIG_FILE = os.path.join(BASE_DIR, "config.json")
PRODUCTS_FILE = os.path.join(DATA_DIR, "products.json")
ORDERS_FILE = os.path.join(DATA_DIR, "orders.json")
USERS_FILE = os.path.join(DATA_DIR, "users.json")
SETTINGS_FILE = os.path.join(DATA_DIR, "settings.json")
SUPPORT_FILE = os.path.join(DATA_DIR, "support_messages.json")
CHANNELS_FILE = os.path.join(DATA_DIR, "channels.json")
DB_FILE = os.path.join(DATA_DIR, "bot_data.sqlite3")
LOG_FILE = os.path.join(LOG_DIR, "bot.log")

os.makedirs(DATA_DIR, exist_ok=True)
os.makedirs(LOG_DIR, exist_ok=True)
os.makedirs(BACKUP_DIR, exist_ok=True)

logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
)

DEFAULT_CONFIG = {
    "admin_ids": [],
    "store_name": "فروشگاه زعفران",
    "currency": "تومان",
    "fallback_order_chat_id": "",
    "transaction_chat_id": "",
    "bank_card_number": "",
    "bank_card_holder": "",
    "bank_name": "",
    "saffron_mesghal_price": "0",
    "saffron_mesghal_prices": {
        "sargol": "0",
        "super_negin": "0",
        "negin_pushal": "0",
        "dokhtar_pich": "0",
        "konj": "0",
    },
    "saffron_type_chat_ids": {
        "Sargol": "",
        "Super Negin": "",
        "Pushal": "",
        "Daste": "",
        "Konj": "",
    },
    "support_username": "",
    "website_url": "",
    "category_titles": {
        "saffron": "زعفران اکبری قائنات",
        "barberry": "زرشک اکبری خراسان جنوبی",
    },
    "auto_backup_enabled": True,
    "auto_backup_interval_minutes": 60,
    "auto_backup_max_files": 30,
    "anti_spam": {
        "support_cooldown_seconds": 120,
        "order_cooldown_seconds": 180,
    },
    "forced_join_enabled": True,
    "saffron_option_shipping_fees": {
        "sargol": {"g1": "0", "g2": "0", "g3": "0", "m1": "0", "m2": "0", "m3": "0", "m5": "0"},
        "super_negin": {"g1": "0", "g2": "0", "g3": "0", "m1": "0", "m2": "0", "m3": "0", "m5": "0"},
        "negin_pushal": {"g1": "0", "g2": "0", "g3": "0", "m1": "0", "m2": "0", "m3": "0", "m5": "0"},
        "dokhtar_pich": {"g1": "0", "g2": "0", "g3": "0", "m1": "0", "m2": "0", "m3": "0", "m5": "0"},
        "konj": {"g1": "0", "g2": "0", "g3": "0", "m1": "0", "m2": "0", "m3": "0", "m5": "0"},
    },
}

DEFAULT_SETTINGS = {
    "welcome_text": "🌹 به فروشگاه زعفران اکبری خوش آمدید.",
    "payment_text": "💳 مبلغ سفارش را واریز کرده و تصویر رسید را ارسال کنید.",
    "terms_text": "📝 برای ثبت سفارش، اطلاعات ارسال را کامل وارد کنید.",
    "help_text": "📘 برای خرید، محصول را انتخاب و مراحل ثبت سفارش را کامل کنید.",
    "help_photo_file_id": "",
}

PRODUCT_CATEGORIES = [
    {"key": "saffron", "default_label": "زعفران اکبری قائنات"},
    {"key": "barberry", "default_label": "زرشک اکبری خراسان جنوبی"},
]
PRODUCT_CATEGORY_KEYS = {item["key"] for item in PRODUCT_CATEGORIES}

MESGHAL_GRAMS = 4.6
SAFFRON_TYPES = [
    {"key": "sargol", "label": "سرگل"},
    {"key": "super_negin", "label": "سوپر نگین"},
    {"key": "negin_pushal", "label": "نگین | پوشال"},
    {"key": "dokhtar_pich", "label": "دسته | دختر پیچ"},
    {"key": "konj", "label": "کنج"},
]
SAFFRON_TYPE_LABEL_TO_KEY = {t["label"]: t["key"] for t in SAFFRON_TYPES}
SAFFRON_GROUP_CONFIG_NAMES = {
    "sargol": "Sargol",
    "super_negin": "Super Negin",
    "negin_pushal": "Pushal",
    "dokhtar_pich": "Daste",
    "konj": "Konj",
}
SAFFRON_OPTIONS = [
    {"key": "g1", "label": "1 گرم", "grams": 1.0},
    {"key": "g2", "label": "2 گرم", "grams": 2.0},
    {"key": "g3", "label": "3 گرم", "grams": 3.0},
    {"key": "m1", "label": "1 مثقال", "grams": 1 * MESGHAL_GRAMS},
    {"key": "m2", "label": "2 مثقال", "grams": 2 * MESGHAL_GRAMS},
    {"key": "m3", "label": "3 مثقال", "grams": 3 * MESGHAL_GRAMS},
    {"key": "m5", "label": "5 مثقال", "grams": 5 * MESGHAL_GRAMS},
]
BACK_TEXT = "⬅️ بازگشت"
CANCEL_TEXT = "❌ لغو عملیات"
MAIN_MENU_TEXT = "🏠 منوی اصلی"
NO_NOTE_TEXT = "بدون توضیح تکمیلی"
SKIP_TEXT = "بعداً تنظیم می‌کنم"
DELETE_PHOTO_TEXT = "حذف عکس"
EMPTY_TEXT = "خالی"
NO_HELP_PHOTO_TEXT = "بدون عکس"
BLOCKED_TEXT = "⛔ حساب شما به دلیل دریافت 5 اخطار مسدود شده است."


states = {}
_backup_in_progress = False
_db_lock = threading.RLock()

SQLITE_JSON_STORES = {
    os.path.abspath(PRODUCTS_FILE): ("products", []),
    os.path.abspath(ORDERS_FILE): ("orders", {}),
    os.path.abspath(USERS_FILE): ("users", {}),
    os.path.abspath(SETTINGS_FILE): ("settings", DEFAULT_SETTINGS),
    os.path.abspath(SUPPORT_FILE): ("support_messages", {}),
    os.path.abspath(CHANNELS_FILE): ("channels", []),
}


def load_env_file(path=ENV_FILE):
    if not os.path.exists(path):
        return
    try:
        with open(path, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                key, value = line.split("=", 1)
                key = key.strip()
                value = value.strip().strip("\"").strip("'")
                if key and key not in os.environ:
                    os.environ[key] = value
    except Exception as e:
        logging.error("load env error: %s", e)


def read_env_value(key):
    load_env_file()
    return os.environ.get(key, "").strip()


def write_env_token(token):
    token = str(token or "").strip()
    if not token:
        return
    lines = []
    if os.path.exists(ENV_FILE):
        try:
            with open(ENV_FILE, "r", encoding="utf-8") as f:
                lines = f.readlines()
        except Exception:
            lines = []
    written = False
    new_lines = []
    for line in lines:
        if line.strip().startswith("BOT_TOKEN="):
            new_lines.append("BOT_TOKEN=%s\n" % token)
            written = True
        else:
            new_lines.append(line)
    if not written:
        new_lines.append("BOT_TOKEN=%s\n" % token)
    with open(ENV_FILE, "w", encoding="utf-8") as f:
        f.writelines(new_lines)
    os.environ["BOT_TOKEN"] = token


def now_str():
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def copy_default_value(default):
    if isinstance(default, dict):
        return default.copy()
    if isinstance(default, list):
        return list(default)
    return default


def sqlite_store_info(path):
    return SQLITE_JSON_STORES.get(os.path.abspath(path))


def get_db_connection():
    os.makedirs(DATA_DIR, exist_ok=True)
    conn = sqlite3.connect(DB_FILE, timeout=30)
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA synchronous=NORMAL")
    conn.execute(
        "CREATE TABLE IF NOT EXISTS json_stores ("
        "name TEXT PRIMARY KEY, "
        "data TEXT NOT NULL, "
        "updated_at TEXT NOT NULL"
        ")"
    )
    return conn


def normalize_loaded_data(data, default):
    if data is None:
        return copy_default_value(default)
    if isinstance(default, dict) and isinstance(data, dict):
        merged = default.copy()
        merged.update(data)
        return merged
    return data


def load_from_sqlite(name, default):
    with _db_lock:
        try:
            with get_db_connection() as conn:
                row = conn.execute("SELECT data FROM json_stores WHERE name=?", (name,)).fetchone()
                if not row:
                    data = copy_default_value(default)
                    conn.execute(
                        "INSERT OR REPLACE INTO json_stores(name, data, updated_at) VALUES(?,?,?)",
                        (name, json.dumps(data, ensure_ascii=False), now_str()),
                    )
                    return data
                data = json.loads(row[0])
                return normalize_loaded_data(data, default)
        except Exception as e:
            logging.error("load sqlite store error for %s: %s", name, e)
            return copy_default_value(default)


def save_to_sqlite(name, data):
    with _db_lock:
        with get_db_connection() as conn:
            conn.execute(
                "INSERT OR REPLACE INTO json_stores(name, data, updated_at) VALUES(?,?,?)",
                (name, json.dumps(data, ensure_ascii=False), now_str()),
            )


def load_legacy_json_file(path, default):
    try:
        if not os.path.exists(path):
            return None
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        return normalize_loaded_data(data, default)
    except Exception as e:
        logging.error("legacy json load error for %s: %s", path, e)
        return None


def migrate_legacy_json_to_sqlite(path, name, default):
    legacy = load_legacy_json_file(path, default)
    if legacy is None:
        return False
    with _db_lock:
        try:
            with get_db_connection() as conn:
                exists = conn.execute("SELECT 1 FROM json_stores WHERE name=?", (name,)).fetchone()
                if exists:
                    return False
                conn.execute(
                    "INSERT OR REPLACE INTO json_stores(name, data, updated_at) VALUES(?,?,?)",
                    (name, json.dumps(legacy, ensure_ascii=False), now_str()),
                )
                return True
        except Exception as e:
            logging.error("legacy json migrate error for %s: %s", path, e)
            return False


def load_json(path, default):
    store = sqlite_store_info(path)
    if store:
        name, store_default = store
        migrate_legacy_json_to_sqlite(path, name, store_default)
        return load_from_sqlite(name, store_default)
    try:
        if not os.path.exists(path):
            save_json(path, default)
            return copy_default_value(default)
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        return normalize_loaded_data(data, default)
    except Exception as e:
        logging.error("load_json error for %s: %s", path, e)
        return copy_default_value(default)


def save_json(path, data):
    store = sqlite_store_info(path)
    if store:
        name, _ = store
        save_to_sqlite(name, data)
        try:
            maybe_auto_backup()
        except Exception as e:
            logging.error("auto backup after sqlite save error: %s", e)
        return
    os.makedirs(os.path.dirname(path), exist_ok=True)
    tmp_path = path + ".tmp"
    with open(tmp_path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
    os.replace(tmp_path, path)


def get_raw_config_for_backup():
    try:
        if os.path.exists(CONFIG_FILE):
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                data = json.load(f)
                return data if isinstance(data, dict) else {}
    except Exception:
        pass
    return {}


def maybe_auto_backup(force=False):
    global _backup_in_progress
    if _backup_in_progress:
        return None
    cfg = get_raw_config_for_backup()
    if not force and cfg.get("auto_backup_enabled", True) is False:
        return None
    try:
        interval_minutes = int(cfg.get("auto_backup_interval_minutes", 60) or 60)
    except Exception:
        interval_minutes = 60
    try:
        max_files = int(cfg.get("auto_backup_max_files", 30) or 30)
    except Exception:
        max_files = 30
    os.makedirs(BACKUP_DIR, exist_ok=True)
    meta_file = os.path.join(BACKUP_DIR, "last_backup.json")
    last_ts = 0
    try:
        if os.path.exists(meta_file):
            with open(meta_file, "r", encoding="utf-8") as f:
                last_ts = float(json.load(f).get("last_ts", 0) or 0)
    except Exception:
        last_ts = 0
    now_ts = time.time()
    if not force and last_ts and (now_ts - last_ts) < max(60, interval_minutes * 60):
        return None
    _backup_in_progress = True
    try:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = os.path.join(BACKUP_DIR, "data_backup_%s.zip" % timestamp)
        with zipfile.ZipFile(backup_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
            if os.path.exists(DB_FILE):
                safe_db_path = os.path.join(BACKUP_DIR, "bot_data_backup_%s.sqlite3" % timestamp)
                try:
                    with sqlite3.connect(DB_FILE, timeout=30) as source, sqlite3.connect(safe_db_path) as target:
                        source.backup(target)
                    zf.write(safe_db_path, arcname=os.path.join("data", "bot_data.sqlite3"))
                finally:
                    try:
                        if os.path.exists(safe_db_path):
                            os.remove(safe_db_path)
                    except Exception:
                        pass
        with open(meta_file, "w", encoding="utf-8") as f:
            json.dump({"last_ts": now_ts, "last_backup": os.path.basename(backup_path)}, f, ensure_ascii=False, indent=2)
        backups = sorted(
            [os.path.join(BACKUP_DIR, x) for x in os.listdir(BACKUP_DIR) if x.startswith("data_backup_") and x.endswith(".zip")],
            key=lambda x: os.path.getmtime(x),
            reverse=True,
        )
        for old in backups[max_files:]:
            try:
                os.remove(old)
            except Exception:
                pass
        return backup_path
    except Exception as e:
        logging.error("auto backup error: %s", e)
        return None
    finally:
        _backup_in_progress = False
def normalize_digits(value):
    if value is None:
        return ""
    table = str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")
    return str(value).translate(table)


def clean_price(value):
    value = normalize_digits(value)
    return (
        value.replace(",", "")
        .replace("٬", "")
        .replace(" ", "")
        .replace("_", "")
        .strip()
    )


def parse_price(value):
    value = clean_price(value)
    if not value.isdigit():
        return None
    return int(value)

def ensure_files():
    load_env_file()
    cfg = load_json(CONFIG_FILE, DEFAULT_CONFIG)
    old_token = str(cfg.pop("bot_token", "") or "").strip()
    if old_token and old_token != "PUT_YOUR_BOT_TOKEN_HERE" and not read_env_value("BOT_TOKEN"):
        write_env_token(old_token)
    old_price = str(cfg.get("saffron_mesghal_price", "0") or "0")
    prices = cfg.get("saffron_mesghal_prices")
    if not isinstance(prices, dict):
        prices = {}
    normalized_prices = {}
    for t in SAFFRON_TYPES:
        normalized_prices[t["key"]] = str(prices.get(t["key"], old_price if old_price != "0" else "0") or "0")
    cfg["saffron_mesghal_prices"] = normalized_prices

    chat_ids = cfg.get("saffron_type_chat_ids")
    if not isinstance(chat_ids, dict):
        chat_ids = {}
    normalized_chat_ids = {}
    for t in SAFFRON_TYPES:
        name = SAFFRON_GROUP_CONFIG_NAMES[t["key"]]
        normalized_chat_ids[name] = str(chat_ids.get(name, "") or "").strip()
    cfg["saffron_type_chat_ids"] = normalized_chat_ids

    option_shipping = cfg.get("saffron_option_shipping_fees")
    if not isinstance(option_shipping, dict):
        option_shipping = {}
    normalized_option_shipping = {}
    for t in SAFFRON_TYPES:
        current = option_shipping.get(t["key"], {})
        if not isinstance(current, dict):
            current = {}
        normalized_option_shipping[t["key"]] = {
            opt["key"]: str(parse_price(current.get(opt["key"], "0")) or 0)
            for opt in SAFFRON_OPTIONS
        }
    cfg["saffron_option_shipping_fees"] = normalized_option_shipping

    category_titles = cfg.get("category_titles")
    if not isinstance(category_titles, dict):
        category_titles = {}
    normalized_category_titles = {}
    for item in PRODUCT_CATEGORIES:
        normalized_category_titles[item["key"]] = str(category_titles.get(item["key"], item["default_label"]) or item["default_label"]).strip()
    cfg["category_titles"] = normalized_category_titles
    save_json(CONFIG_FILE, cfg)
    load_json(PRODUCTS_FILE, [])
    load_json(ORDERS_FILE, {})
    load_json(USERS_FILE, {})
    load_json(SUPPORT_FILE, {})
    load_json(CHANNELS_FILE, [])
    settings = load_json(SETTINGS_FILE, DEFAULT_SETTINGS)
    save_json(SETTINGS_FILE, settings)


ensure_files()
maybe_auto_backup(force=True)
load_env_file()
config = load_json(CONFIG_FILE, DEFAULT_CONFIG)

BOT_TOKEN = os.environ.get("BOT_TOKEN", "").strip()
if not BOT_TOKEN or BOT_TOKEN == "PUT_YOUR_BOT_TOKEN_HERE":
    raise RuntimeError("BOT_TOKEN تنظیم نشده است. فایل .env را باز کن و BOT_TOKEN را با توکن BotFather پر کن.")

bot = telebot.TeleBot(BOT_TOKEN, parse_mode=None)


def setup_bot_commands():
    try:
        bot.set_my_commands([types.BotCommand("start", "شروع ربات")])
    except Exception as e:
        logging.error("set bot commands error: %s", e)


def reload_config():
    global config
    config = load_json(CONFIG_FILE, DEFAULT_CONFIG)
    return config


def get_admin_ids():
    cfg = reload_config()
    return set(str(x) for x in cfg.get("admin_ids", []))


def is_admin(user_id):
    return str(user_id) in get_admin_ids()


def get_store_name():
    return reload_config().get("store_name", "فروشگاه")


def get_currency():
    return reload_config().get("currency", "تومان")


def parse_float(value):
    value = normalize_digits(value)
    value = value.replace("٫", ".").replace("/", ".").replace(",", ".").strip()
    m = re.search(r"\d+(?:\.\d+)?", value)
    if not m:
        return None
    try:
        return float(m.group(0))
    except Exception:
        return None


def parse_quantity(value):
    number = parse_float(value)
    if number is None or number <= 0:
        return None
    return number


def parse_order_count(value):
    value = clean_price(value)
    if not value.isdigit():
        return None
    count = int(value)
    if count <= 0 or count > 100:
        return None
    return count


def fmt_price(value):
    try:
        if value is None or value == "":
            value = 0
        if isinstance(value, str):
            value = clean_price(value)
        return "{:,}".format(int(round(float(value))))
    except Exception:
        return str(value)


def fmt_decimal(value):
    try:
        value = float(value)
        if value.is_integer():
            return str(int(value))
        return ("%.3f" % value).rstrip("0").rstrip(".")
    except Exception:
        return str(value)


def html_escape(text):
    if text is None:
        return ""
    text = str(text)
    return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")


def get_user_label(user_id):
    users = load_json(USERS_FILE, {})
    u = users.get(str(user_id), {})
    name = u.get("first_name") or "-"
    username = u.get("username")
    if username:
        return "%s (@%s)" % (name, username)
    return name


def get_support_user_display(message):
    user = message.from_user
    if getattr(user, "username", None):
        return "@%s" % user.username
    full_name = " ".join([x for x in [user.first_name or "", user.last_name or ""] if x]).strip()
    return full_name or str(user.id)


def create_support_message(message, text):
    support_id = uuid.uuid4().hex[:10]
    data = load_json(SUPPORT_FILE, {})
    data[support_id] = {
        "id": support_id,
        "user_id": str(message.from_user.id),
        "username": message.from_user.username or "",
        "name": " ".join([x for x in [message.from_user.first_name or "", message.from_user.last_name or ""] if x]).strip(),
        "display": get_support_user_display(message),
        "text": text,
        "status": "new",
        "created_at": now_str(),
        "updated_at": now_str(),
    }
    save_json(SUPPORT_FILE, data)
    return data[support_id]


def get_support_message(support_id):
    return load_json(SUPPORT_FILE, {}).get(str(support_id))


def update_support_message(support_id, updates):
    data = load_json(SUPPORT_FILE, {})
    item = data.get(str(support_id))
    if not item:
        return None
    item.update(updates)
    item["updated_at"] = now_str()
    data[str(support_id)] = item
    save_json(SUPPORT_FILE, data)
    return item


def support_admin_text(item):
    username_or_name = item.get("display") or item.get("name") or "-"
    return (
        "📞 <b>پیام پشتیبانی</b>\n"
        "🆔 آیدی عددی: <code>%s</code>\n"
        "👤 کاربر: %s\n"
        "📝 پیام:\n%s"
    ) % (
        html_escape(item.get("user_id", "-")),
        html_escape(username_or_name),
        html_escape(item.get("text", "")),
    )


def support_admin_markup(support_id):
    markup = types.InlineKeyboardMarkup()
    markup.add(
        types.InlineKeyboardButton("⚠️ اخطار", callback_data="support_warn:%s" % support_id),
        types.InlineKeyboardButton("✍️ پاسخ", callback_data="support_reply:%s" % support_id),
    )
    return markup


def send_support_to_admins(item):
    ok = False
    for admin_id in get_admin_ids():
        try:
            bot.send_message(admin_id, support_admin_text(item), parse_mode="HTML", reply_markup=support_admin_markup(item["id"]))
            ok = True
        except Exception as e:
            logging.error("send support to admin %s error: %s", admin_id, e)
    return ok


def ensure_user(message):
    if not getattr(message, "from_user", None):
        return
    users = load_json(USERS_FILE, {})
    u = message.from_user
    uid = str(u.id)
    old = users.get(uid, {}) if isinstance(users.get(uid, {}), dict) else {}
    record = dict(old)
    record.update({
        "id": uid,
        "first_name": u.first_name or old.get("first_name", ""),
        "last_name": u.last_name or old.get("last_name", ""),
        "username": u.username or old.get("username", ""),
        "last_seen": now_str(),
        "created_at": old.get("created_at") or now_str(),
        "warning_count": int(old.get("warning_count", 0) or 0),
        "blocked": bool(old.get("blocked", False)),
    })
    users[uid] = record
    save_json(USERS_FILE, users)


def get_user_record(user_id):
    users = load_json(USERS_FILE, {})
    item = users.get(str(user_id), {})
    return item if isinstance(item, dict) else {}


def save_user_record(user_id, updates):
    users = load_json(USERS_FILE, {})
    uid = str(user_id)
    record = users.get(uid, {}) if isinstance(users.get(uid, {}), dict) else {}
    record.update(updates)
    record["id"] = uid
    record["updated_at"] = now_str()
    if not record.get("created_at"):
        record["created_at"] = now_str()
    users[uid] = record
    save_json(USERS_FILE, users)
    return record


def get_anti_spam_seconds(kind):
    cfg = reload_config()
    anti = cfg.get("anti_spam", {}) if isinstance(cfg.get("anti_spam", {}), dict) else {}
    defaults = DEFAULT_CONFIG["anti_spam"]
    key = "support_cooldown_seconds" if kind == "support" else "order_cooldown_seconds"
    try:
        return max(0, int(anti.get(key, defaults.get(key, 0)) or 0))
    except Exception:
        return int(defaults.get(key, 0) or 0)


def anti_spam_remaining(user_id, kind):
    record = get_user_record(user_id)
    field = "last_support_message_ts" if kind == "support" else "last_order_created_ts"
    try:
        last_ts = float(record.get(field, 0) or 0)
    except Exception:
        last_ts = 0
    cooldown = get_anti_spam_seconds(kind)
    if cooldown <= 0 or last_ts <= 0:
        return 0
    remaining = int(round(cooldown - (time.time() - last_ts)))
    return max(0, remaining)


def mark_anti_spam_action(user_id, kind):
    field = "last_support_message_ts" if kind == "support" else "last_order_created_ts"
    return save_user_record(user_id, {field: time.time()})


def anti_spam_wait_text(kind, seconds):
    seconds = max(1, int(seconds))
    if seconds >= 60:
        minutes = (seconds + 59) // 60
        value = "%s دقیقه" % minutes
    else:
        value = "%s ثانیه" % seconds
    if kind == "support":
        return "⏳ لطفاً %s دیگر پیام پشتیبانی ارسال کنید." % value
    return "⏳ لطفاً %s دیگر سفارش جدید ثبت کنید." % value


def user_warning_count(user_id):
    record = get_user_record(user_id)
    try:
        return int(record.get("warning_count", 0) or 0)
    except Exception:
        return 0


def is_user_blocked(user_id):
    if is_admin(user_id):
        return False
    record = get_user_record(user_id)
    return bool(record.get("blocked", False)) or user_warning_count(user_id) >= 5


def add_user_warning(user_id):
    uid = str(user_id)
    record = get_user_record(uid)
    count = int(record.get("warning_count", 0) or 0)
    if not bool(record.get("blocked", False)):
        count = min(count + 1, 5)
    blocked = count >= 5
    updates = {"warning_count": count, "last_warning_at": now_str()}
    if blocked:
        updates["blocked"] = True
        updates["blocked_at"] = record.get("blocked_at") or now_str()
    save_user_record(uid, updates)
    return count, blocked


def clear_user_warnings(user_id, admin_id=None):
    updates = {"warning_count": 0, "warnings_cleared_at": now_str()}
    if admin_id is not None:
        updates["warnings_cleared_by"] = str(admin_id)
    return save_user_record(user_id, updates)


def ban_user(user_id, admin_id=None):
    updates = {"blocked": True, "blocked_at": now_str()}
    if admin_id is not None:
        updates["blocked_by"] = str(admin_id)
    return save_user_record(user_id, updates)


def unban_user(user_id, admin_id=None):
    updates = {"blocked": False, "warning_count": 0, "unblocked_at": now_str()}
    if admin_id is not None:
        updates["unblocked_by"] = str(admin_id)
    return save_user_record(user_id, updates)


def user_orders_summary(user_id):
    items = [o for o in get_orders().values() if str(o.get("user_id")) == str(user_id)]
    done_statuses = {"accepted", "preparing", "posted", "delivered"}
    total = len(items)
    accepted = sum(1 for o in items if o.get("status") in done_statuses)
    pending = sum(1 for o in items if o.get("status") == "pending")
    rejected = sum(1 for o in items if o.get("status") == "rejected")
    total_amount = 0
    for o in items:
        if o.get("status") in done_statuses:
            price = parse_price(o.get("total_price", "0"))
            if price is not None:
                total_amount += price
    return {
        "total": total,
        "accepted": accepted,
        "pending": pending,
        "rejected": rejected,
        "total_amount": total_amount,
    }


def user_profile_text(user_id):
    uid = str(user_id)
    record = get_user_record(uid)
    summary = user_orders_summary(uid)
    first_name = record.get("first_name") or "-"
    last_name = record.get("last_name") or ""
    name = (first_name + " " + last_name).strip() or "-"
    username = record.get("username") or ""
    username_text = "@" + username if username else "-"
    warning_count = int(record.get("warning_count", 0) or 0)
    blocked = is_user_blocked(uid)
    status = "⛔ مسدود" if blocked else "✅ فعال"
    return (
        "👤 <b>مدیریت کاربر</b>\n"
        "🆔 آیدی: <code>%s</code>\n"
        "👤 نام: %s\n"
        "🔗 یوزرنیم: %s\n"
        "📌 وضعیت: %s\n"
        "⚠️ اخطارها: %s/5\n"
        "🧾 خریدها: %s | ✅ %s | ⏳ %s | ❌ %s\n"
        "💰 مجموع خرید تأییدشده: %s %s"
    ) % (
        html_escape(uid),
        html_escape(name),
        html_escape(username_text),
        html_escape(status),
        warning_count,
        summary["total"],
        summary["accepted"],
        summary["pending"],
        summary["rejected"],
        fmt_price(summary["total_amount"]),
        html_escape(get_currency()),
    )


def user_admin_markup(user_id):
    uid = str(user_id)
    markup = types.InlineKeyboardMarkup()
    markup.row(
        types.InlineKeyboardButton("✉️ ارسال پیام", callback_data="u_msg:%s" % uid),
        types.InlineKeyboardButton("⚠️ اخطار", callback_data="u_warn:%s" % uid),
    )
    markup.row(
        types.InlineKeyboardButton("🧾 تعداد خریدها", callback_data="u_orders:%s" % uid),
        types.InlineKeyboardButton("🧹 حذف اخطارها", callback_data="u_clearwarn:%s" % uid),
    )
    markup.row(
        types.InlineKeyboardButton("⛔ بن", callback_data="u_ban:%s" % uid),
        types.InlineKeyboardButton("✅ رفع بن", callback_data="u_unban:%s" % uid),
    )
    return markup


def send_user_admin_panel(chat_id, user_id, edit_message=None):
    text = user_profile_text(user_id)
    markup = user_admin_markup(user_id)
    if edit_message is not None:
        try:
            return bot.edit_message_text(text, chat_id=edit_message.chat.id, message_id=edit_message.message_id, parse_mode="HTML", reply_markup=markup)
        except Exception:
            pass
    return bot.send_message(chat_id, text, parse_mode="HTML", reply_markup=markup)


def valid_user_id_text(value):
    value = normalize_digits(value or "").strip()
    return value if value.isdigit() else ""


def main_keyboard(user_id):
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🛍 مشاهده محصولات", "🔎 پیگیری سفارش")
    kb.row("🌐 وبسایت زعفران اکبری", "📘 راهنمای انواع زعفران")
    kb.row("🧾 سفارش‌های من", "📞 پشتیبانی")
    if is_admin(user_id):
        kb.row("⚙️ پنل ادمین")
    return kb


def back_cancel_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def optional_back_cancel_keyboard(optional_text=SKIP_TEXT):
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row(optional_text)
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def help_photo_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row(NO_HELP_PHOTO_TEXT, DELETE_PHOTO_TEXT)
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def product_category_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    for item in PRODUCT_CATEGORIES:
        kb.row(get_product_category_label(item["key"]))
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def pricing_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🌺 زعفران با گزینه‌های آماده")
    kb.row("💰 قیمت دستی")
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def saffron_type_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("سرگل", "سوپر نگین")
    kb.row("نگین | پوشال")
    kb.row("دسته | دختر پیچ")
    kb.row("کنج")
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def saffron_price_type_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🌺 قیمت سرگل", "🌺 قیمت سوپر نگین")
    kb.row("🌺 قیمت نگین | پوشال")
    kb.row("🌺 قیمت دسته | دختر پیچ")
    kb.row("🌺 قیمت کنج")
    kb.row(BACK_TEXT)
    return kb


def saffron_shipping_type_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🚚 ارسال سرگل", "🚚 ارسال سوپر نگین")
    kb.row("🚚 ارسال نگین | پوشال")
    kb.row("🚚 ارسال دسته | دختر پیچ")
    kb.row("🚚 ارسال کنج")
    kb.row(BACK_TEXT)
    return kb


def saffron_group_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🧾 گروه سرگل", "🧾 گروه سوپر نگین")
    kb.row("🧾 گروه نگین | پوشال")
    kb.row("🧾 گروه دسته | دختر پیچ")
    kb.row("🧾 گروه کنج")
    kb.row(BACK_TEXT)
    return kb


def saffron_options_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("1 گرم", "2 گرم", "3 گرم")
    kb.row("1 مثقال", "2 مثقال")
    kb.row("3 مثقال", "5 مثقال")
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def quantity_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("1", "2", "3")
    kb.row("4", "5", "10")
    kb.row(BACK_TEXT, CANCEL_TEXT)
    return kb


def admin_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("➕ افزودن محصول", "📦 مدیریت محصولات")
    kb.row("🧾 سفارش‌ها", "📊 آمار")
    kb.row("⚙️ تنظیمات", "📣 پیام همگانی")
    kb.row(MAIN_MENU_TEXT)
    return kb


def settings_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🌺 تنظیمات زعفران", "💳 تنظیمات پرداخت")
    kb.row("🏪 تنظیمات فروشگاه")
    kb.row(BACK_TEXT)
    return kb


def saffron_settings_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🌺 تنظیم قیمت هر مثقال")
    kb.row("🚚 هزینه ارسال وزن‌ها")
    kb.row("🧾 تنظیم گروه‌های اختصاصی زعفران")
    kb.row(BACK_TEXT)
    return kb


def payment_settings_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("💳 تنظیم شماره کارت", "👤 تنظیم صاحب کارت")
    kb.row("🏛 تنظیم نام بانک", "💳 تنظیم گروه تراکنش")
    kb.row("🏦 تنظیم متن پرداخت/رسید")
    kb.row(BACK_TEXT)
    return kb


def store_settings_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("🏪 تنظیم نام فروشگاه", "💱 تنظیم واحد پول")
    kb.row("📘 تنظیم راهنما", "🌐 تنظیم وبسایت")
    kb.row("🏷 تنظیم عنوان دسته‌ها")
    kb.row("🚚 هزینه ارسال محصولات")
    kb.row("📢 جوین اجباری")
    kb.row("🧾 تنظیم گروه پیش‌فرض سفارشات")
    kb.row(BACK_TEXT)
    return kb


def category_title_settings_keyboard():
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.row("عنوان زعفران", "عنوان زرشک")
    kb.row(BACK_TEXT)
    return kb


def product_shipping_settings_markup():
    markup = types.InlineKeyboardMarkup(row_width=1)
    products = get_products(False)
    for product in products:
        title = product.get("title") or "بدون عنوان"
        if product.get("pricing_type") == "saffron_options":
            label = "%s | ارسال وزن‌ها" % title
        else:
            fee = get_product_shipping_fee(product)
            label = "%s | ارسال: %s %s" % (title, fmt_price(fee), get_currency())
        markup.add(types.InlineKeyboardButton(label[:60], callback_data="prod_edit:shipping_fee:%s" % product.get("id")))
    if not products:
        markup.add(types.InlineKeyboardButton("محصولی ثبت نشده است", callback_data="noop"))
    return markup


def get_product_category_by_key(key):
    for item in PRODUCT_CATEGORIES:
        if item["key"] == str(key):
            return item
    return None


def get_product_category_label(key):
    item = get_product_category_by_key(key)
    if not item:
        return "دسته‌بندی نامشخص"
    cfg = reload_config()
    titles = cfg.get("category_titles") if isinstance(cfg.get("category_titles"), dict) else {}
    value = str(titles.get(item["key"], "") or "").strip()
    return value or item["default_label"]


def get_product_category_by_button(text):
    text = (text or "").strip().replace("ي", "ی").replace("ك", "ک")
    text = " ".join(text.split())
    for item in PRODUCT_CATEGORIES:
        label = get_product_category_label(item["key"])
        default_label = item["default_label"]
        if text == label or text == default_label:
            return item
    return None


def product_category_key(product):
    if not product:
        return ""
    category = str(product.get("category", "") or "").strip()
    if category in PRODUCT_CATEGORY_KEYS:
        return category
    if product.get("pricing_type") in ["saffron_options", "saffron_gram"]:
        return "saffron"

    return "barberry"


def filter_products_by_category(products, category_key):
    category_key = str(category_key or "").strip()
    return [p for p in products if product_category_key(p) == category_key]


def category_has_active_products(category_key):
    return bool(filter_products_by_category(get_products(active_only=True), category_key))


def available_product_categories(active_only=True):
    products = get_products(active_only=active_only)
    available = []
    for item in PRODUCT_CATEGORIES:
        if filter_products_by_category(products, item["key"]):
            available.append(item)
    return available


def get_products(active_only=False):
    products = load_json(PRODUCTS_FILE, [])
    if not isinstance(products, list):
        products = []
    if active_only:
        products = [p for p in products if p.get("active", True)]
    return products


def save_products(products):
    save_json(PRODUCTS_FILE, products)


def get_product(pid):
    for p in get_products(False):
        if str(p.get("id")) == str(pid):
            return p
    return None


def update_product(pid, updates):
    products = get_products(False)
    for i, p in enumerate(products):
        if str(p.get("id")) == str(pid):
            p.update(updates)
            products[i] = p
            save_products(products)
            return p
    return None


def delete_product(pid):
    products = get_products(False)
    new_products = [p for p in products if str(p.get("id")) != str(pid)]
    save_products(new_products)
    return len(new_products) != len(products)


def get_saffron_type_by_key(key):
    for item in SAFFRON_TYPES:
        if item["key"] == str(key):
            return item
    return None


def get_saffron_type_label(key):
    item = get_saffron_type_by_key(key)
    return item["label"] if item else "نامشخص"


def get_saffron_type_by_label(label):
    label = (label or "").strip().replace("ي", "ی").replace("ك", "ک")
    label = " ".join(label.split())
    for item in SAFFRON_TYPES:
        if item["label"] == label:
            return item
    return None


def get_saffron_type_key_from_price_button(text):
    text = (text or "").strip()
    if not text.startswith("🌺 قیمت "):
        return None
    label = text.replace("🌺 قیمت", "", 1).strip()
    item = get_saffron_type_by_label(label)
    return item["key"] if item else None


def get_saffron_type_key_from_group_button(text):
    text = (text or "").strip()
    if not text.startswith("🧾 گروه "):
        return None
    label = text.replace("🧾 گروه", "", 1).strip()
    item = get_saffron_type_by_label(label)
    return item["key"] if item else None


def get_saffron_type_key_from_shipping_button(text):
    text = (text or "").strip()
    if not text.startswith("🚚 ارسال "):
        return None
    label = text.replace("🚚 ارسال", "", 1).strip()
    item = get_saffron_type_by_label(label)
    return item["key"] if item else None


def get_saffron_option_shipping_fees(saffron_type_key):
    cfg = reload_config()
    all_fees = cfg.get("saffron_option_shipping_fees", {})
    fees = all_fees.get(str(saffron_type_key), {}) if isinstance(all_fees, dict) else {}
    if not isinstance(fees, dict):
        fees = {}
    return fees


def get_saffron_option_shipping_fee(saffron_type_key, option_key):
    fees = get_saffron_option_shipping_fees(saffron_type_key)
    return parse_price(fees.get(str(option_key), "0")) or 0


def saffron_shipping_summary_lines(saffron_type_key):
    currency = get_currency()
    fees = get_saffron_option_shipping_fees(saffron_type_key)
    lines = []
    for opt in SAFFRON_OPTIONS:
        fee = parse_price(fees.get(opt["key"], "0")) or 0
        lines.append("• %s: %s %s" % (opt["label"], fmt_price(fee), currency))
    return "\n".join(lines)


def saffron_shipping_all_types_summary_lines():
    lines = []
    for item in SAFFRON_TYPES:
        fees = get_saffron_option_shipping_fees(item["key"])
        values = [parse_price(fees.get(opt["key"], "0")) or 0 for opt in SAFFRON_OPTIONS]
        if values and len(set(values)) == 1:
            summary = "%s %s" % (fmt_price(values[0]), get_currency())
        else:
            summary = "تنظیم جداگانه"
        lines.append("• %s: %s" % (item["label"], summary))
    return "\n".join(lines)


def parse_saffron_shipping_fees_text(text):
    value = normalize_digits(str(text or "").strip())
    if not value:
        return None
    numbers = re.findall(r"\d+", value.replace(",", ""))
    if len(numbers) == 1:
        fee = int(numbers[0])
        return {opt["key"]: str(fee) for opt in SAFFRON_OPTIONS}
    if len(numbers) != len(SAFFRON_OPTIONS):
        return None
    return {opt["key"]: str(int(numbers[i])) for i, opt in enumerate(SAFFRON_OPTIONS)}


def saffron_shipping_prompt(saffron_type_key):
    label = get_saffron_type_label(saffron_type_key)
    return (
        "🚚 هزینه ارسال «%s» را وارد کنید.\n"
        "برای ثبت سریع، یا یک عدد برای همه وزن‌ها بفرستید، یا 7 عدد به همین ترتیب:\n"
        "1 گرم | 2 گرم | 3 گرم | 1 مثقال | 2 مثقال | 3 مثقال | 5 مثقال\n\n"
        "نمونه:\n"
        "150000 150000 150000 0 0 0 0\n\n"
        "وضعیت فعلی:\n%s"
    ) % (label, saffron_shipping_summary_lines(saffron_type_key))


def get_saffron_group_config_name(saffron_type_key):
    return SAFFRON_GROUP_CONFIG_NAMES.get(str(saffron_type_key), "")


def get_saffron_group_chat_id(saffron_type_key):
    cfg = reload_config()
    name = get_saffron_group_config_name(saffron_type_key)
    chat_ids = cfg.get("saffron_type_chat_ids", {})
    if isinstance(chat_ids, dict) and name:
        return str(chat_ids.get(name, "") or "").strip()
    return ""


def saffron_groups_summary_lines():
    cfg = reload_config()
    chat_ids = cfg.get("saffron_type_chat_ids", {}) if isinstance(cfg.get("saffron_type_chat_ids"), dict) else {}
    lines = []
    for item in SAFFRON_TYPES:
        name = get_saffron_group_config_name(item["key"])
        value = str(chat_ids.get(name, "") or "").strip() or "تنظیم نشده"
        lines.append("• %s (%s): %s" % (item["label"], name, value))
    return "\n".join(lines)


def saffron_prices_summary_lines():
    currency = get_currency()
    cfg = reload_config()
    prices = cfg.get("saffron_mesghal_prices", {}) if isinstance(cfg.get("saffron_mesghal_prices"), dict) else {}
    lines = []
    for item in SAFFRON_TYPES:
        price = prices.get(item["key"], cfg.get("saffron_mesghal_price", "0"))
        lines.append("• %s: %s %s" % (item["label"], fmt_price(price), currency))
    return "\n".join(lines)


def get_saffron_option_by_key(key):
    for opt in SAFFRON_OPTIONS:
        if opt["key"] == str(key):
            return opt
    return None


def get_saffron_option_by_label(label):
    label = normalize_digits(str(label or "").strip())
    label = label.replace("ي", "ی").replace("ك", "ک")
    label = " ".join(label.split())
    for opt in SAFFRON_OPTIONS:
        if normalize_digits(opt["label"]) == label:
            return opt

    compact = label.replace(" ", "")
    aliases = {
        "1گرم": "g1", "2گرم": "g2", "3گرم": "g3",
        "1مثقال": "m1", "2مثقال": "m2", "3مثقال": "m3", "5مثقال": "m5",
    }
    key = aliases.get(compact)
    return get_saffron_option_by_key(key) if key else None


def get_product_shipping_fee(product):
    if not product:
        return 0
    return parse_price(product.get("shipping_fee", "0")) or 0


def is_saffron_gram_option(option_or_key):
    opt = option_or_key if isinstance(option_or_key, dict) else get_saffron_option_by_key(option_or_key)
    return bool(opt and str(opt.get("key", "")).startswith("g"))


def saffron_option_base_price(option_or_key, saffron_type_key=None):
    opt = option_or_key if isinstance(option_or_key, dict) else get_saffron_option_by_key(option_or_key)
    if not opt:
        return 0
    mesghal_price = get_saffron_mesghal_price(saffron_type_key)
    if mesghal_price <= 0:
        return 0
    return int(round((mesghal_price / MESGHAL_GRAMS) * float(opt.get("grams", 0))))


def saffron_option_price(option_or_key, saffron_type_key=None):
    return saffron_option_base_price(option_or_key, saffron_type_key)


def get_order_shipping_fee(product, saffron_option_key=""):
    if product and product.get("pricing_type") == "saffron_options" and saffron_option_key:
        return get_saffron_option_shipping_fee(product.get("saffron_type", ""), saffron_option_key)
    return get_product_shipping_fee(product)


def saffron_options_price_lines(saffron_type_key=None):
    currency = get_currency()
    lines = []
    for opt in SAFFRON_OPTIONS:
        lines.append("• %s: %s %s" % (opt["label"], fmt_price(saffron_option_price(opt, saffron_type_key)), currency))
    return "\n".join(lines)


def saffron_options_price_compact(saffron_type_key=None):
    currency = get_currency()
    parts = []
    for opt in SAFFRON_OPTIONS:
        parts.append("%s: %s" % (opt["label"].replace(" ", ""), fmt_price(saffron_option_price(opt, saffron_type_key))))
    return " | ".join(parts) + " " + currency


def is_saffron_options_product(product):
    return product and product.get("pricing_type") == "saffron_options"


def get_saffron_mesghal_price(saffron_type_key=None):
    cfg = reload_config()
    prices = cfg.get("saffron_mesghal_prices", {})
    if isinstance(prices, dict) and saffron_type_key:
        return parse_price(prices.get(str(saffron_type_key), "0")) or 0
    if isinstance(prices, dict):

        for item in SAFFRON_TYPES:
            price = parse_price(prices.get(item["key"], "0")) or 0
            if price > 0:
                return price
    return parse_price(cfg.get("saffron_mesghal_price", "0")) or 0


def product_unit_price(product, saffron_option_key=None):
    if not product:
        return 0
    if product.get("pricing_type") == "saffron_options":
        if not saffron_option_key:
            return 0
        return saffron_option_price(saffron_option_key, product.get("saffron_type", ""))
    if product.get("pricing_type") == "saffron_gram":
        mesghal_price = get_saffron_mesghal_price(product.get("saffron_type", ""))
        weight = parse_float(product.get("weight_grams")) or 0
        if mesghal_price <= 0 or weight <= 0:
            return 0
        return int(round((mesghal_price / MESGHAL_GRAMS) * weight))
    return parse_price(product.get("price")) or 0


def calc_order_total(product, amount_text=None, saffron_option_key=None):
    if product and product.get("pricing_type") == "saffron_options":
        unit = product_unit_price(product, saffron_option_key=saffron_option_key)
        qty = parse_order_count(amount_text) if amount_text not in [None, ""] else 1
        if unit <= 0 or qty is None:
            return None, None
        total = int(round(unit * qty)) + get_order_shipping_fee(product, saffron_option_key=saffron_option_key)
        return total, qty

    unit = product_unit_price(product)
    qty = parse_quantity(amount_text)
    if unit <= 0 or qty is None:
        return None, None
    total = int(round(unit * qty)) + get_order_shipping_fee(product)
    return total, qty

def product_caption(product, admin=False):
    currency = get_currency()
    unit_price = product_unit_price(product)
    status = "فعال" if product.get("active", True) else "غیرفعال"


    text = ""
    if admin or product.get("pricing_type") != "saffron_options":
        text = "📦 <b>%s</b>\n" % html_escape(product.get("title", "بدون عنوان"))

    if product.get("pricing_type") == "saffron_options":
        saffron_type = product.get("saffron_type", "sargol")
        mesghal = get_saffron_mesghal_price(saffron_type)
        text += "🌺 نوع: <b>%s</b>\n" % html_escape(get_saffron_type_label(saffron_type))
        text += "💰 قیمت‌ها:\n%s\n" % html_escape(saffron_options_price_lines(saffron_type))
        if mesghal <= 0:
            text += "⚠️ قیمت روز این نوع تنظیم نشده است.\n"
    elif product.get("pricing_type") == "saffron_gram":
        weight = product.get("weight_grams", "")
        saffron_type = product.get("saffron_type", "sargol")
        text += "🌺 نوع: <b>%s</b> | وزن: <b>%s گرم</b>\n" % (
            html_escape(get_saffron_type_label(saffron_type)), html_escape(fmt_decimal(weight))
        )
        text += "💰 قیمت: <b>%s %s</b>\n" % (fmt_price(unit_price), html_escape(currency))
    else:
        text += "💰 قیمت: <b>%s %s</b>\n" % (fmt_price(unit_price), html_escape(currency))

    if product.get("pricing_type") == "saffron_options":
        if admin:
            text += "\n🚚 هزینه ارسال وزن‌ها:\n%s\n" % html_escape(saffron_shipping_summary_lines(product.get("saffron_type", "")))
    else:
        shipping_fee = get_product_shipping_fee(product)
        if shipping_fee > 0:
            text += "\n🚚 هزینه ارسال: <b>%s %s</b>\n" % (fmt_price(shipping_fee), html_escape(currency))

    if product.get("description"):
        text += "\n📝 <b>%s</b>\n" % html_escape(product.get("description"))

    if admin:
        text += "⚙️ وضعیت: %s" % status
        if product.get("pricing_type") == "saffron_options":
            text += " | گزینه‌ای"
        elif product.get("pricing_type") == "saffron_gram":
            text += " | مثقال روز"
        else:
            text += " | دستی"
        if product.get("pricing_type") in ["saffron_options", "saffron_gram"]:
            saffron_group = get_saffron_group_chat_id(product.get("saffron_type", ""))
            group_label = get_saffron_group_config_name(product.get("saffron_type", "")) or "-"
            text += "\n🧾 گروه: %s | %s" % (html_escape(group_label), html_escape(saffron_group or "تنظیم نشده"))
        else:
            text += "\n🧾 گروه: عمومی"
    return text

def product_categories_markup():
    markup = types.InlineKeyboardMarkup(row_width=1)
    for item in available_product_categories(active_only=True):
        markup.add(types.InlineKeyboardButton(
            get_product_category_label(item["key"]),
            callback_data="product_category:%s" % item["key"],
        ))
    return markup


def products_list_markup(products, category_key=None):
    markup = types.InlineKeyboardMarkup(row_width=1)
    for product in products:
        title = product.get("title") or "بدون عنوان"
        markup.add(types.InlineKeyboardButton("🪻 %s" % title, callback_data="view_product:%s" % product.get("id")))
    markup.add(types.InlineKeyboardButton("⬅️ بازگشت", callback_data="product_categories"))
    return markup


def admin_products_list_markup(products):
    markup = types.InlineKeyboardMarkup(row_width=1)
    for product in products:
        status = "✅" if product.get("active", True) else "⛔"
        title = product.get("title") or "بدون عنوان"
        category = get_product_category_label(product_category_key(product))
        markup.add(types.InlineKeyboardButton(
            "%s %s | %s" % (status, title, category),
            callback_data="adm_prod:%s" % product.get("id"),
        ))
    return markup


def replace_inline_message(call, text, reply_markup=None, parse_mode="HTML"):
    chat_id = call.message.chat.id
    message_id = call.message.message_id
    try:
        bot.edit_message_text(
            text,
            chat_id=chat_id,
            message_id=message_id,
            parse_mode=parse_mode,
            reply_markup=reply_markup,
        )
        return True
    except Exception as e:
        logging.info("edit_message_text fallback: %s", e)
        try:
            bot.delete_message(chat_id, message_id)
        except Exception as delete_error:
            logging.info("delete_message fallback failed: %s", delete_error)
        bot.send_message(chat_id, text, parse_mode=parse_mode, reply_markup=reply_markup)
        return False


def delete_inline_message(call):
    try:
        bot.delete_message(call.message.chat.id, call.message.message_id)
        return True
    except Exception as e:
        logging.info("delete_inline_message failed: %s", e)
        return False


def send_admin_products_list(chat_id):
    products = get_products(False)
    if not products:
        return bot.send_message(chat_id, "⚠️ محصولی ثبت نشده است.", reply_markup=admin_keyboard())
    bot.send_message(chat_id, "📦 محصول را برای مدیریت انتخاب کنید.", reply_markup=admin_products_list_markup(products))


def send_product_categories(chat_id, call=None):
    categories = available_product_categories(active_only=True)
    if not categories:
        text = "⚠️ محصول فعالی ثبت نشده است."
        markup = None
    else:
        text = "🛍 <b>دسته‌بندی محصول را انتخاب کنید.</b>"
        markup = product_categories_markup()
    if call is not None:
        return replace_inline_message(call, text, reply_markup=markup, parse_mode="HTML")
    bot.send_message(chat_id, text, parse_mode="HTML", reply_markup=markup)


def build_products_title_list(category_key=None):
    all_products = get_products(active_only=True)
    if category_key:
        products = filter_products_by_category(all_products, category_key)
        category_label = get_product_category_label(category_key)
    else:
        products = all_products
        category_label = "محصولات فروشگاه"

    if not products:
        markup = types.InlineKeyboardMarkup()
        markup.add(types.InlineKeyboardButton("⬅️ بازگشت", callback_data="product_categories"))
        text = "⚠️ محصول فعالی در بخش «%s» ثبت نشده است." % category_label
        return text, markup

    text = "🛍 <b>%s</b>" % html_escape(category_label)
    return text, products_list_markup(products, category_key)


def send_products_title_list(chat_id, category_key=None, call=None):
    text, markup = build_products_title_list(category_key)
    if call is not None:
        return replace_inline_message(call, text, reply_markup=markup, parse_mode="HTML")
    bot.send_message(chat_id, text, parse_mode="HTML", reply_markup=markup)


def send_product_card(chat_id, product, admin=False, back_to_products=False):
    markup = types.InlineKeyboardMarkup()
    pid = product.get("id")
    if admin:
        markup.add(types.InlineKeyboardButton("✏️ مدیریت / ویرایش", callback_data="adm_prod:%s" % pid))
    else:
        markup.add(types.InlineKeyboardButton("🛒 ثبت درخواست خرید", callback_data="buy:%s" % pid))
        if back_to_products:
            category_key = product_category_key(product)
            markup.add(types.InlineKeyboardButton("⬅️ بازگشت", callback_data="products_list:%s" % category_key))
    caption = product_caption(product, admin=admin)
    photo = product.get("photo_file_id")
    try:
        if photo:
            bot.send_photo(chat_id, photo, caption=caption, parse_mode="HTML", reply_markup=markup)
        else:
            bot.send_message(chat_id, caption, parse_mode="HTML", reply_markup=markup)
    except Exception as e:
        logging.error("send_product_card error: %s", e)
        bot.send_message(chat_id, "⚠️ نمایش محصول ناموفق بود.")


def get_orders():
    data = load_json(ORDERS_FILE, {})
    if not isinstance(data, dict):
        data = {}
    return data


def save_orders(data):
    save_json(ORDERS_FILE, data)


def get_order(order_id):
    return get_orders().get(str(order_id))


def update_order(order_id, updates):
    orders = get_orders()
    oid = str(order_id)
    if oid not in orders:
        return None
    orders[oid].update(updates)
    save_orders(orders)
    return orders[oid]


def order_status_map():
    return {
        "pending": "در انتظار بررسی",
        "accepted": "تأیید شده",
        "preparing": "درحال آماده‌سازی",
        "posted": "تحویل پست شده",
        "delivered": "تحویل مشتری شده",
        "rejected": "رد شده",
    }


def order_customer_status_text(order):
    status = order.get("status")
    tracking_code = order.get("tracking_code") or "-"
    product_title = order.get("product_title") or "-"
    amount = order.get("saffron_option_label") or order.get("amount") or "-"
    if order.get("product_pricing_type") == "saffron_options" and order.get("quantity_number"):
        amount = "%s × %s" % (amount, fmt_decimal(order.get("quantity_number")))
    status_messages = {
        "pending": "⏳ سفارش شما در انتظار بررسی فروشگاه است.",
        "accepted": "✅ سفارش شما تأیید شده و در صف آماده‌سازی است.",
        "preparing": "📦 سفارش شما درحال آماده‌سازی می‌باشد.",
        "posted": "🚚 سفارش شما تحویل پست گردید.",
        "delivered": "✅ سفارش تحویل مشتری گردید.",
        "rejected": "❌ سفارش شما تأیید نشده است.",
    }
    text = "🔎 <b>پیگیری سفارش</b>\n"
    text += "کد پیگیری: <code>%s</code>\n" % html_escape(tracking_code)
    text += "محصول: %s\n" % html_escape(product_title)
    text += "مقدار: %s\n" % html_escape(amount)
    text += "وضعیت: %s" % html_escape(status_messages.get(status, order_status_map().get(status, "نامشخص")))
    return text

def generate_tracking_code():
    orders = get_orders()
    existing = {str(o.get("tracking_code", "")).upper() for o in orders.values()}
    for _ in range(50):
        code = "ZA" + datetime.now().strftime("%m%d") + uuid.uuid4().hex[:6].upper()
        if code not in existing:
            return code
    return "ZA" + uuid.uuid4().hex[:10].upper()


def ensure_tracking_code(order_id):
    order = get_order(order_id)
    if not order:
        return None
    if order.get("tracking_code"):
        return order
    return update_order(order_id, {"tracking_code": generate_tracking_code(), "tracking_created_at": now_str()})


def find_order_by_tracking_code(code):
    code = normalize_digits(str(code or "")).strip().upper().replace(" ", "")
    if not code:
        return None
    for order in get_orders().values():
        if str(order.get("tracking_code", "")).strip().upper() == code:
            return order
    return None


def order_admin_markup(order):
    status = order.get("status")
    order_id = order.get("id")
    markup = types.InlineKeyboardMarkup()
    if status == "pending":
        markup.add(
            types.InlineKeyboardButton("✅ تایید سفارش", callback_data="order_ok:%s" % order_id),
            types.InlineKeyboardButton("❌ رد سفارش", callback_data="order_no:%s" % order_id),
        )
    elif status == "accepted":
        markup.add(types.InlineKeyboardButton("📦 درحال آماده‌سازی سفارش", callback_data="order_stage:preparing:%s" % order_id))
    elif status == "preparing":
        markup.add(types.InlineKeyboardButton("🚚 سفارش شما تحویل پست گردید", callback_data="order_stage:posted:%s" % order_id))
    elif status == "posted":
        markup.add(types.InlineKeyboardButton("✅ سفارش تحویل مشتری گردید", callback_data="order_stage:delivered:%s" % order_id))
    elif status == "delivered":
        markup.add(types.InlineKeyboardButton("✅ تکمیل شد", callback_data="noop"))
    elif status == "rejected":
        markup.add(types.InlineKeyboardButton("❌ رد شد", callback_data="noop"))
    return markup


def notify_order_status(order):
    try:
        bot.send_message(order.get("user_id"), order_customer_status_text(order), parse_mode="HTML", reply_markup=main_keyboard(int(order.get("user_id"))))
        return True
    except Exception as e:
        logging.error("notify order status error: %s", e)
        return False


def order_text(order, status_text=None):
    status_map = order_status_map()
    status = status_text or status_map.get(order.get("status"), order.get("status", "-"))
    username = order.get("username") or ""
    username = "@" + username if username else "-"
    currency = get_currency()

    text = "🆕 <b>درخواست خرید</b>\n\n"
    if order.get("tracking_code"):
        text += "🔎 کد پیگیری: <code>%s</code>\n" % html_escape(order.get("tracking_code"))
    text += "📦 محصول: <b>%s</b>\n" % html_escape(order.get("product_title"))
    if order.get("product_category_label"):
        text += "🏷 دسته‌بندی: <b>%s</b>\n" % html_escape(order.get("product_category_label"))
    if order.get("product_pricing_type") == "saffron_options":
        if order.get("saffron_type_label"):
            text += "🌺 نوع زعفران: <b>%s</b>\n" % html_escape(order.get("saffron_type_label"))
        text += "⚖️ گزینه انتخابی مشتری: <b>%s</b>\n" % html_escape(order.get("saffron_option_label", order.get("amount", "-")))
        if order.get("quantity_number"):
            text += "🔢 تعداد: <b>%s</b>\n" % html_escape(fmt_decimal(order.get("quantity_number")))
        text += "🌺 قیمت مثقال همین نوع هنگام ثبت: %s %s\n" % (fmt_price(order.get("saffron_mesghal_price_at_order", "0")), html_escape(currency))
        text += "💰 قیمت واحد انتخابی: <b>%s %s</b>\n" % (fmt_price(order.get("product_price", "0")), html_escape(currency))
        if order.get("quantity_number"):
            subtotal = int(round((parse_price(order.get("product_price", "0")) or 0) * (parse_quantity(order.get("quantity_number")) or 1)))
            text += "🧮 مبلغ محصول: <b>%s %s</b>\n" % (fmt_price(subtotal), html_escape(currency))
    else:
        if order.get("product_pricing_type") == "saffron_gram":
            text += "⚖️ وزن محصول: %s گرم\n" % html_escape(fmt_decimal(order.get("product_weight_grams", "")))
            if order.get("saffron_type_label"):
                text += "🌺 نوع زعفران: %s\n" % html_escape(order.get("saffron_type_label"))
            text += "🌺 قیمت مثقال همین نوع هنگام ثبت: %s %s\n" % (fmt_price(order.get("saffron_mesghal_price_at_order", "0")), html_escape(currency))
        text += "💰 قیمت واحد محصول: %s %s\n" % (fmt_price(order.get("product_price", "0")), html_escape(currency))
        text += "🔢 تعداد/مقدار درخواستی: <b>%s</b>\n" % html_escape(order.get("amount"))
        if order.get("quantity_number"):
            text += "🧮 تعداد عددی تشخیص‌داده‌شده: %s\n" % html_escape(fmt_decimal(order.get("quantity_number")))
    if parse_price(order.get("shipping_fee", "0")):
        text += "🚚 هزینه ارسال: <b>%s %s</b>\n" % (fmt_price(order.get("shipping_fee")), html_escape(currency))
    if order.get("total_price"):
        text += "💳 مبلغ قابل پرداخت/اعلامی: <b>%s %s</b>\n" % (fmt_price(order.get("total_price")), html_escape(currency))

    text += "\n👤 نام مشتری: %s\n" % html_escape(order.get("full_name"))
    text += "📞 شماره تماس: <code>%s</code>\n" % html_escape(order.get("phone"))
    text += "📍 آدرس کامل:\n%s\n" % html_escape(order.get("address", "-"))
    text += "🏷 کد پستی: <code>%s</code>\n" % html_escape(order.get("postal_code", "-"))
    text += "🆔 تلگرام ID: <code>%s</code>\n" % html_escape(order.get("user_id"))
    text += "🔗 یوزرنیم: %s\n" % html_escape(username)
    if order.get("note"):
        text += "\n📝 توضیحات تکمیلی مشتری:\n%s\n" % html_escape(order.get("note"))

    if order.get("paid_card_number"):
        text += "\n💳 شماره کارت اعلام‌شده: <code>%s</code>\n" % html_escape(order.get("paid_card_number"))
    if order.get("paid_card_holder"):
        text += "👤 صاحب کارت: %s\n" % html_escape(order.get("paid_card_holder"))
    if order.get("paid_bank_name"):
        text += "🏛 بانک: %s\n" % html_escape(order.get("paid_bank_name"))
    if order.get("receipt_caption"):
        text += "🧾 توضیح/کپشن رسید:\n%s\n" % html_escape(order.get("receipt_caption"))
    if order.get("receipt_file_id"):
        text += "🖼 رسید واریزی: ارسال شده\n"

    text += "\n📌 وضعیت: <b>%s</b>\n" % html_escape(status)
    text += "⏰ زمان ثبت: %s" % html_escape(order.get("created_at"))
    return text


def get_order_destination(product):
    cfg = reload_config()
    fallback = str(cfg.get("fallback_order_chat_id", "")).strip()
    return fallback


def get_transaction_destination(order=None):
    cfg = reload_config()
    product = get_product(order.get("product_id")) if order else None

    saffron_type_key = ""
    if order:
        saffron_type_key = str(order.get("saffron_type_key", "") or "").strip()
    if not saffron_type_key and product and product.get("pricing_type") in ["saffron_options", "saffron_gram"]:
        saffron_type_key = str(product.get("saffron_type", "") or "").strip()

    if saffron_type_key:
        dedicated = get_saffron_group_chat_id(saffron_type_key)
        if dedicated:
            return dedicated

    transaction_chat_id = str(cfg.get("transaction_chat_id", "")).strip()
    if transaction_chat_id:
        return transaction_chat_id
    return get_order_destination(product)


def format_bank_info():
    cfg = reload_config()
    card = str(cfg.get("bank_card_number", "")).strip()
    holder = str(cfg.get("bank_card_holder", "")).strip()
    lines = []
    if card:
        lines.append("💳 شماره کارت: <code>%s</code>" % html_escape(card))
    else:
        lines.append("💳 شماره کارت هنوز توسط مدیریت فروشگاه تنظیم نشده است.")
    if holder:
        lines.append("👤 صاحب کارت: %s" % html_escape(holder))
    return "\n".join(lines)


def payment_request_text(product, amount=None, saffron_option_key=None, quantity_count=None):
    settings = load_json(SETTINGS_FILE, DEFAULT_SETTINGS)
    currency = get_currency()
    text = "✅ <b>پرداخت سفارش</b>\n"
    text += "📦 %s\n" % html_escape(product.get("title"))

    if product.get("pricing_type") == "saffron_options":
        opt = get_saffron_option_by_key(saffron_option_key)
        total, qty = calc_order_total(product, quantity_count, saffron_option_key=saffron_option_key)
        unit = product_unit_price(product, saffron_option_key=saffron_option_key)
        label = opt.get("label") if opt else amount or "-"
        text += "⚖️ %s × %s\n" % (html_escape(label), html_escape(fmt_decimal(qty or quantity_count or 1)))
        if total is not None:
            subtotal = int(round(unit * (qty or 1)))
            text += "💰 مبلغ محصول: %s %s\n" % (fmt_price(subtotal), html_escape(currency))
            shipping_fee = get_order_shipping_fee(product, saffron_option_key=saffron_option_key)
            if shipping_fee > 0:
                text += "🚚 هزینه ارسال: %s %s\n" % (fmt_price(shipping_fee), html_escape(currency))
            text += "💸 مبلغ کل: <b>%s %s</b>\n" % (fmt_price(total), html_escape(currency))
    else:
        total, qty = calc_order_total(product, amount)
        text += "🔢 %s\n" % html_escape(amount)
        if total is not None:
            subtotal = int(round((product_unit_price(product) or 0) * (qty or 1)))
            text += "💰 مبلغ محصول: %s %s\n" % (fmt_price(subtotal), html_escape(currency))
            shipping_fee = get_order_shipping_fee(product)
            if shipping_fee > 0:
                text += "🚚 هزینه ارسال: %s %s\n" % (fmt_price(shipping_fee), html_escape(currency))
            text += "💸 مبلغ کل: <b>%s %s</b>\n" % (fmt_price(total), html_escape(currency))
        else:
            text += "💰 مبلغ توسط مدیریت بررسی می‌شود.\n"

    text += "\n" + format_bank_info()
    text += "\n\n🧾 تصویر رسید واریزی را ارسال کنید."
    return text

def send_receipt_to_chat(chat_id, order):
    receipt_file_id = order.get("receipt_file_id")
    if not receipt_file_id:
        return None
    caption = "🖼 رسید واریزی سفارش %s" % order.get("id")
    try:
        if order.get("receipt_file_type") == "document":
            return bot.send_document(chat_id, receipt_file_id, caption=caption)
        return bot.send_photo(chat_id, receipt_file_id, caption=caption)
    except Exception as e:
        logging.error("send_receipt_to_chat error: %s", e)
        return None


def send_order_to_destination(order):
    destination = get_transaction_destination(order)
    markup = order_admin_markup(order)
    text = order_text(order)

    if destination:
        try:
            receipt_msg = send_receipt_to_chat(destination, order)
            msg = bot.send_message(destination, text, parse_mode="HTML", reply_markup=markup)
            updates = {"order_message_chat_id": msg.chat.id, "order_message_id": msg.message_id}
            if receipt_msg:
                updates["receipt_message_chat_id"] = receipt_msg.chat.id
                updates["receipt_message_id"] = receipt_msg.message_id
            update_order(order["id"], updates)
            return True
        except Exception as e:
            logging.error("send_order_to_destination error: %s", e)

    ok = False
    for admin_id in get_admin_ids():
        try:
            send_receipt_to_chat(admin_id, order)
            bot.send_message(admin_id, text, parse_mode="HTML", reply_markup=markup)
            ok = True
        except Exception as e:
            logging.error("send order to admin %s error: %s", admin_id, e)
    return ok


def create_order(user_id, username, product, amount, full_name, phone, address, postal_code, note, receipt_file_id="", receipt_file_type="photo", receipt_caption="", saffron_option_key="", quantity_count="1"):
    order_id = str(int(time.time())) + uuid.uuid4().hex[:4]
    cfg = reload_config()
    opt = get_saffron_option_by_key(saffron_option_key) if product.get("pricing_type") == "saffron_options" else None
    saffron_type = product.get("saffron_type", "") if product.get("pricing_type") in ["saffron_options", "saffron_gram"] else ""
    if opt:
        amount = opt.get("label")
    calc_amount = quantity_count if opt else amount
    unit = product_unit_price(product, saffron_option_key=saffron_option_key)
    total, qty = calc_order_total(product, calc_amount, saffron_option_key=saffron_option_key)
    base_unit = unit
    if product.get("pricing_type") == "saffron_options" and saffron_option_key:
        base_unit = saffron_option_base_price(saffron_option_key, saffron_type)
    elif product.get("pricing_type") == "saffron_gram":
        base_unit = product_unit_price(product)
    order = {
        "id": order_id,
        "user_id": str(user_id),
        "username": username or "",
        "product_id": product.get("id"),
        "product_title": product.get("title"),
        "product_category": product_category_key(product),
        "product_category_label": get_product_category_label(product_category_key(product)),
        "product_price": str(base_unit),
        "product_pricing_type": product.get("pricing_type", "manual"),
        "product_weight_grams": product.get("weight_grams", ""),
        "saffron_type_key": saffron_type,
        "saffron_type_label": get_saffron_type_label(saffron_type) if saffron_type else "",
        "saffron_option_key": saffron_option_key or "",
        "saffron_option_label": opt.get("label") if opt else "",
        "saffron_option_grams": str(opt.get("grams")) if opt else "",
        "saffron_mesghal_price_at_order": str(get_saffron_mesghal_price(saffron_type)),
        "amount": amount,
        "quantity_number": str(qty) if qty is not None else "",
        "total_price": str(total) if total is not None else "",
        "shipping_fee": str(get_order_shipping_fee(product, saffron_option_key=saffron_option_key)),
        "full_name": full_name,
        "phone": phone,
        "address": address,
        "postal_code": postal_code,
        "note": note,
        "receipt_file_id": receipt_file_id or "",
        "receipt_file_type": receipt_file_type or "photo",
        "receipt_caption": receipt_caption or "",
        "paid_card_number": str(cfg.get("bank_card_number", "")).strip(),
        "paid_card_holder": str(cfg.get("bank_card_holder", "")).strip(),
        "paid_bank_name": str(cfg.get("bank_name", "")).strip(),
        "tracking_code": "",
        "status": "pending",
        "created_at": now_str(),
        "updated_at": now_str(),
    }
    orders = get_orders()
    orders[order_id] = order
    save_orders(orders)
    return order


def pending_orders_keyboard():
    orders = get_orders()
    pending = [o for o in orders.values() if o.get("status") == "pending"]
    pending.sort(key=lambda x: x.get("created_at", ""), reverse=True)
    markup = types.InlineKeyboardMarkup()
    for o in pending[:30]:
        title = "%s | %s" % (o.get("product_title", "محصول"), o.get("phone", ""))
        markup.add(types.InlineKeyboardButton(title[:55], callback_data="view_order:%s" % o.get("id")))
    return markup, pending


def send_order_step_prompt(chat_id, state):
    product = get_product(state.get("product_id"))
    step = state.get("step")
    if not product:
        bot.send_message(chat_id, "⚠️ ⚠️ محصول پیدا نشد.")
        return
    if step == "saffron_option":
        saffron_type = product.get("saffron_type", "sargol")
        text = "🌺 مقدار زعفران را انتخاب کنید."
        if get_saffron_mesghal_price(saffron_type) <= 0:
            text = "⚠️ قیمت روز این نوع هنوز تنظیم نشده است."
        bot.send_message(chat_id, text, reply_markup=saffron_options_keyboard())
    elif step == "amount":
        bot.send_message(chat_id, "🔢 تعداد یا مقدار را وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "quantity":
        bot.send_message(chat_id, "🔢 تعداد را انتخاب یا وارد کنید.", reply_markup=quantity_keyboard())
    elif step == "full_name":
        bot.send_message(chat_id, "👤 نام و نام خانوادگی گیرنده را وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "phone":
        bot.send_message(chat_id, "📞 شماره تماس گیرنده را وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "address":
        bot.send_message(chat_id, "📍 آدرس کامل گیرنده را وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "postal_code":
        bot.send_message(chat_id, "🏷 کد پستی ۱۰ رقمی را وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "note":
        bot.send_message(chat_id, "📝 توضیحات تکمیلی را بنویسید.", reply_markup=optional_back_cancel_keyboard(NO_NOTE_TEXT))
    elif step == "receipt":
        bot.send_message(
            chat_id,
            payment_request_text(
                product,
                state.get("data", {}).get("amount", ""),
                state.get("data", {}).get("saffron_option_key", ""),
                state.get("data", {}).get("quantity", "1"),
            ),
            parse_mode="HTML",
            reply_markup=back_cancel_keyboard(),
        )

def send_add_product_step_prompt(chat_id, state):
    step = state.get("step")
    data = state.get("data", {})
    if step == "category":
        bot.send_message(chat_id, "🏷 دسته‌بندی محصول را انتخاب کنید.", reply_markup=product_category_keyboard())
    elif step == "title":
        bot.send_message(chat_id, "📦 عنوان محصول را وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "pricing_mode":
        bot.send_message(chat_id, "💰 نوع قیمت‌گذاری را انتخاب کنید.", reply_markup=pricing_keyboard())
    elif step == "saffron_type":
        bot.send_message(chat_id, "🌺 نوع زعفران را انتخاب کنید.", reply_markup=saffron_type_keyboard())
    elif step == "weight_grams":
        bot.send_message(chat_id, "⚖️ وزن را به گرم وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "price":
        bot.send_message(chat_id, "💰 قیمت را عددی وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "description":
        bot.send_message(chat_id, "📝 توضیحات محصول را وارد کنید.", reply_markup=back_cancel_keyboard())
    elif step == "photo":
        bot.send_message(chat_id, "📷 عکس محصول را ارسال کنید.", reply_markup=optional_back_cancel_keyboard(SKIP_TEXT))
    elif step == "chat":
        bot.send_message(chat_id, "🧾 chat_id گروه سفارش را وارد کنید.", reply_markup=optional_back_cancel_keyboard(SKIP_TEXT))

def handle_state_back(message, state):
    uid = str(message.from_user.id)
    flow = state.get("flow")
    step = state.get("step")

    if flow == "order":
        product = get_product(state.get("product_id"))
        previous = {
            "saffron_option": None,
            "quantity": "saffron_option",
            "amount": None,
            "full_name": "quantity" if is_saffron_options_product(product) else "amount",
            "phone": "full_name",
            "address": "phone",
            "postal_code": "address",
            "note": "postal_code",
            "receipt": "note",
        }.get(step)
        if not previous:
            states.pop(uid, None)
            return bot.send_message(message.chat.id, "🏠 منوی اصلی", reply_markup=main_keyboard(message.from_user.id))
        state["step"] = previous
        return send_order_step_prompt(message.chat.id, state)

    if flow == "add_product":
        data = state.get("data", {})
        previous = {
            "category": None,
            "title": "category",
            "pricing_mode": "title",
            "saffron_type": "pricing_mode",
            "weight_grams": "saffron_type",
            "price": "title" if data.get("category") == "barberry" else "pricing_mode",
            "description": "saffron_type" if data.get("pricing_type") == "saffron_options" else ("weight_grams" if data.get("pricing_type") == "saffron_gram" else "price"),
            "photo": "description",
        }.get(step)
        if not previous:
            states.pop(uid, None)
            return bot.send_message(message.chat.id, "⚙️ پنل ادمین", reply_markup=admin_keyboard())
        state["step"] = previous
        return send_add_product_step_prompt(message.chat.id, state)

    if flow == "support_message":
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "🏠 منوی اصلی", reply_markup=main_keyboard(message.from_user.id))

    if flow == "track_order":
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "🏠 منوی اصلی", reply_markup=main_keyboard(message.from_user.id))

    if flow == "force_channel":
        if state.get("step") == "chat_id":
            state["step"] = "name"
            return bot.send_message(message.chat.id, "📢 نام کانال را وارد کنید.", reply_markup=back_cancel_keyboard())
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚙️ تنظیمات فروشگاه", reply_markup=store_settings_keyboard())

    if flow == "support_reply":
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚙️ پنل ادمین", reply_markup=admin_keyboard())

    if flow == "setting" and step == "help_photo":
        state["step"] = "help_text"
        return bot.send_message(message.chat.id, "📘 متن راهنما را وارد کنید.", reply_markup=back_cancel_keyboard())

    if flow in ["edit_product", "config", "setting", "broadcast"]:
        states.pop(uid, None)
        kb = settings_keyboard() if flow in ["config", "setting"] else admin_keyboard()
        return bot.send_message(message.chat.id, "⬅️ مرحله قبل", reply_markup=kb)

    states.pop(uid, None)
    return bot.send_message(message.chat.id, "🏠 منوی اصلی", reply_markup=main_keyboard(message.from_user.id))


def get_force_channels():
    channels = load_json(CHANNELS_FILE, [])
    if not isinstance(channels, list):
        return []
    return channels


def save_force_channels(channels):
    save_json(CHANNELS_FILE, channels)


def is_forced_join_enabled():
    return bool(reload_config().get("forced_join_enabled", True))


def channel_link_from_chat(chat, channel_input=""):
    username = getattr(chat, "username", None)
    if username:
        return "https://t.me/%s" % username
    text = (channel_input or "").strip()
    if text.startswith("https://t.me/"):
        return text
    try:
        return bot.export_chat_invite_link(chat.id)
    except Exception as e:
        logging.error("export invite link error for %s: %s", getattr(chat, "id", ""), e)
    return ""


def resolve_force_channel(channel_input):
    text = (channel_input or "").strip()
    if not text:
        return None, "ورودی معتبر نیست."
    try:
        if text.startswith("https://t.me/+") or "joinchat" in text:
            return None, "برای کانال خصوصی، chat_id عددی کانال را ارسال کنید."
        if text.startswith("https://t.me/") or text.startswith("@"):
            username = text.split("/")[-1] if text.startswith("https://") else text[1:]
            chat = bot.get_chat("@%s" % username)
            return {
                "chat_id": str(chat.id),
                "username": "@%s" % username,
                "link": channel_link_from_chat(chat, text),
            }, ""
        if text.lstrip("-").isdigit():
            chat = bot.get_chat(int(text))
            username = getattr(chat, "username", None)
            return {
                "chat_id": str(chat.id),
                "username": "@%s" % username if username else "",
                "link": channel_link_from_chat(chat, text),
            }, ""
    except Exception as e:
        logging.error("resolve force channel error: %s", e)
        return None, "کانال پیدا نشد یا ربات دسترسی لازم را ندارد."
    return None, "آیدی یا یوزرنیم کانال معتبر نیست."


def check_force_join_membership(user_id):
    if not is_forced_join_enabled() or is_admin(user_id):
        return []
    not_joined = []
    for channel in get_force_channels():
        chat_id = channel.get("chat_id")
        if not chat_id:
            continue
        try:
            member = bot.get_chat_member(chat_id, user_id)
            if member.status not in ["member", "administrator", "creator"]:
                not_joined.append(channel)
        except Exception as e:
            logging.error("check force join error for user %s channel %s: %s", user_id, chat_id, e)
            not_joined.append(channel)
    return not_joined


def show_force_join_buttons(chat_id, user_id, not_joined_channels=None):
    not_joined_channels = not_joined_channels if not_joined_channels is not None else check_force_join_membership(user_id)
    if not not_joined_channels:
        return False
    kb = types.InlineKeyboardMarkup()
    for channel in not_joined_channels:
        link = (channel.get("link") or "").strip()
        if not link and channel.get("username"):
            link = "https://t.me/%s" % str(channel.get("username")).lstrip("@")
        if link:
            kb.add(types.InlineKeyboardButton(channel.get("name") or "عضویت در کانال", url=link))
        else:
            kb.add(types.InlineKeyboardButton(channel.get("name") or "عضویت در کانال", callback_data="force_join_no_link"))
    kb.add(types.InlineKeyboardButton("✅ عضو شدم", callback_data="check_membership"))
    bot.send_message(chat_id, "📢 برای استفاده از ربات، ابتدا عضو کانال شوید.", reply_markup=kb)
    return True


def send_force_channels_panel(chat_id, edit_message=None):
    channels = get_force_channels()
    kb = types.InlineKeyboardMarkup()
    for channel in channels:
        kb.add(types.InlineKeyboardButton(channel.get("name") or str(channel.get("chat_id")), callback_data="force_channel_view:%s" % channel.get("id")))
    kb.add(types.InlineKeyboardButton("➕ افزودن کانال", callback_data="force_channel_add"))
    text = "📢 کانال‌های جوین اجباری را مدیریت کنید."
    if edit_message:
        return bot.edit_message_text(text, chat_id=edit_message.chat.id, message_id=edit_message.message_id, reply_markup=kb)
    return bot.send_message(chat_id, text, reply_markup=kb)


def force_channel_detail_text(channel):
    return (
        "📢 <b>%s</b>\n"
        "🆔 <code>%s</code>\n"
        "🔗 %s\n"
        "👤 %s\n"
        "📊 عضویت‌های موفق: %s"
    ) % (
        html_escape(channel.get("name") or "کانال"),
        html_escape(channel.get("chat_id") or ""),
        html_escape(channel.get("link") or "بدون لینک"),
        html_escape(channel.get("username") or "-"),
        html_escape(str(channel.get("successful_joins", 0))),
    )

def send_help_content(chat_id, reply_markup=None):
    settings = load_json(SETTINGS_FILE, DEFAULT_SETTINGS)
    text = (settings.get("help_text") or DEFAULT_SETTINGS["help_text"]).strip()
    photo = (settings.get("help_photo_file_id") or "").strip()
    if photo:
        try:
            return bot.send_photo(chat_id, photo, caption=text, reply_markup=reply_markup)
        except Exception as e:
            logging.error("send help photo error: %s", e)
    return bot.send_message(chat_id, text, reply_markup=reply_markup)


@bot.message_handler(func=lambda m: getattr(m, "from_user", None) and is_user_blocked(m.from_user.id), content_types=["text", "photo", "document", "audio", "video", "voice", "contact", "location", "sticker", "video_note", "animation"])
def blocked_user_message(message):
    states.pop(str(message.from_user.id), None)
    if getattr(message, "chat", None) and message.chat.type == "private":
        bot.send_message(message.chat.id, BLOCKED_TEXT, reply_markup=types.ReplyKeyboardRemove())


@bot.callback_query_handler(func=lambda c: getattr(c, "from_user", None) and is_user_blocked(c.from_user.id))
def blocked_user_callback(call):
    try:
        bot.answer_callback_query(call.id, BLOCKED_TEXT, show_alert=True)
    except Exception:
        pass


@bot.callback_query_handler(func=lambda c: getattr(c, "data", None) == "check_membership")
def handle_check_membership(call):
    user_id = call.from_user.id
    not_joined = check_force_join_membership(user_id)
    if not not_joined:
        channels = get_force_channels()
        for channel in channels:
            channel["successful_joins"] = int(channel.get("successful_joins", 0) or 0) + 1
        save_force_channels(channels)
        try:
            bot.delete_message(call.message.chat.id, call.message.message_id)
        except Exception:
            pass
        bot.answer_callback_query(call.id, "عضویت تایید شد.")
        return bot.send_message(call.message.chat.id, "✅ عضویت تایید شد.", reply_markup=main_keyboard(user_id))
    bot.answer_callback_query(call.id, "هنوز عضو همه کانال‌ها نیستید.", show_alert=True)
    try:
        bot.delete_message(call.message.chat.id, call.message.message_id)
    except Exception:
        pass
    show_force_join_buttons(call.message.chat.id, user_id, not_joined)


@bot.callback_query_handler(func=lambda c: getattr(c, "data", None) == "force_join_no_link")
def handle_force_join_no_link(call):
    bot.answer_callback_query(call.id, "لینک عضویت این کانال تنظیم نشده است.", show_alert=True)


@bot.message_handler(func=lambda m: getattr(m, "from_user", None) and getattr(m, "chat", None) and m.chat.type == "private" and not is_admin(m.from_user.id) and bool(check_force_join_membership(m.from_user.id)), content_types=["text", "photo", "document", "audio", "video", "voice", "contact", "location", "sticker", "video_note", "animation"])
def force_join_message_guard(message):
    states.pop(str(message.from_user.id), None)
    show_force_join_buttons(message.chat.id, message.from_user.id)


@bot.callback_query_handler(func=lambda c: getattr(c, "from_user", None) and getattr(c, "data", None) not in ["check_membership", "force_join_no_link"] and not is_admin(c.from_user.id) and bool(check_force_join_membership(c.from_user.id)))
def force_join_callback_guard(call):
    try:
        bot.answer_callback_query(call.id, "ابتدا عضو کانال شوید.", show_alert=True)
    except Exception:
        pass
    if getattr(call, "message", None):
        show_force_join_buttons(call.message.chat.id, call.from_user.id)


@bot.message_handler(commands=["start"])
def cmd_start(message):
    ensure_user(message)
    settings = load_json(SETTINGS_FILE, DEFAULT_SETTINGS)
    bot.send_message(
        message.chat.id,
        settings.get("welcome_text") or DEFAULT_SETTINGS["welcome_text"],
        reply_markup=main_keyboard(message.from_user.id),
    )


@bot.message_handler(commands=["myid"])
def cmd_myid(message):
    ensure_user(message)
    bot.send_message(message.chat.id, "🆔 <code>%s</code>" % message.from_user.id, parse_mode="HTML")


@bot.message_handler(commands=["chatid"])
def cmd_chatid(message):
    text = "🆔 <code>%s</code>" % message.chat.id
    bot.send_message(message.chat.id, text, parse_mode="HTML")


@bot.message_handler(commands=["u"])
def cmd_user_admin(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    parts = (message.text or "").split(maxsplit=1)
    if len(parts) < 2:
        return bot.send_message(message.chat.id, "فرمت درست: /u 123456789")
    target_id = valid_user_id_text(parts[1])
    if not target_id:
        return bot.send_message(message.chat.id, "🆔 آیدی عددی معتبر وارد کنید.")
    send_user_admin_panel(message.chat.id, target_id)


@bot.channel_post_handler(commands=["chatid"])
def channel_chatid(message):
    text = "آیدی این کانال:\n<code>%s</code>" % message.chat.id
    try:
        bot.send_message(message.chat.id, text, parse_mode="HTML")
    except Exception:
        pass


@bot.message_handler(func=lambda m: getattr(m, "text", None) == CANCEL_TEXT)
def handle_cancel(message):
    states.pop(str(message.from_user.id), None)
    bot.send_message(message.chat.id, "❌ عملیات لغو شد.", reply_markup=main_keyboard(message.from_user.id))


@bot.message_handler(func=lambda m: getattr(m, "text", None) == BACK_TEXT)
def handle_back(message):
    uid = str(message.from_user.id)
    if uid in states:
        return handle_state_back(message, states[uid])
    if is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⚙️ پنل ادمین", reply_markup=admin_keyboard())
    return bot.send_message(message.chat.id, "🏠 منوی اصلی", reply_markup=main_keyboard(message.from_user.id))


@bot.message_handler(func=lambda m: getattr(m, "text", None) == MAIN_MENU_TEXT)
def handle_main_menu(message):
    states.pop(str(message.from_user.id), None)
    bot.send_message(message.chat.id, "🏠 منوی اصلی", reply_markup=main_keyboard(message.from_user.id))


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🛍 مشاهده محصولات")
def show_products(message):
    ensure_user(message)
    products = get_products(active_only=True)
    if not products:
        return bot.send_message(message.chat.id, "⚠️ محصولی ثبت نشده است.", reply_markup=main_keyboard(message.from_user.id))
    send_product_categories(message.chat.id)


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🧾 سفارش‌های من")
def my_orders(message):
    ensure_user(message)
    uid = str(message.from_user.id)
    orders = [o for o in get_orders().values() if str(o.get("user_id")) == uid]
    if not orders:
        return bot.send_message(message.chat.id, "🧾 سفارشی ثبت نشده است.", reply_markup=main_keyboard(message.from_user.id))
    orders.sort(key=lambda x: x.get("created_at", ""), reverse=True)
    status_map = order_status_map()
    lines = ["🧾 سفارش‌های شما:"]
    for o in orders[:15]:
        tracking = o.get("tracking_code") or "پس از تایید صادر می‌شود"
        lines.append("\nکد پیگیری: %s\nمحصول: %s\nمقدار/تعداد: %s\nوضعیت: %s" % (
            tracking, o.get("product_title"), o.get("amount"), status_map.get(o.get("status"), o.get("status"))
        ))
    bot.send_message(message.chat.id, "\n".join(lines), reply_markup=main_keyboard(message.from_user.id))

@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🔎 پیگیری سفارش")
def track_order_button(message):
    ensure_user(message)
    uid = str(message.from_user.id)
    states[uid] = {"flow": "track_order", "step": "code"}
    bot.send_message(message.chat.id, "🔎 کد پیگیری سفارش را وارد کنید.", reply_markup=back_cancel_keyboard())


def handle_track_order_state(message, state):
    uid = str(message.from_user.id)
    if message.content_type != "text":
        return bot.send_message(message.chat.id, "🔎 کد پیگیری را به‌صورت متن وارد کنید.", reply_markup=back_cancel_keyboard())
    code = (message.text or "").strip()
    order = find_order_by_tracking_code(code)
    if not order or (str(order.get("user_id")) != uid and not is_admin(message.from_user.id)):
        return bot.send_message(message.chat.id, "⚠️ سفارشی با این کد پیگیری پیدا نشد.", reply_markup=back_cancel_keyboard())
    states.pop(uid, None)
    return bot.send_message(message.chat.id, order_customer_status_text(order), parse_mode="HTML", reply_markup=main_keyboard(message.from_user.id))


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "📘 راهنمای انواع زعفران")
def help_only(message):
    ensure_user(message)
    send_help_content(message.chat.id, reply_markup=main_keyboard(message.from_user.id))


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🌐 وبسایت زعفران اکبری")
def website_button(message):
    ensure_user(message)
    cfg = reload_config()
    url = (cfg.get("website_url") or "").strip()
    if not url:
        return bot.send_message(message.chat.id, "🌐 آدرس وبسایت هنوز ثبت نشده است.", reply_markup=main_keyboard(message.from_user.id))
    bot.send_message(message.chat.id, "🌐 وبسایت زعفران اکبری:\n%s" % url, reply_markup=main_keyboard(message.from_user.id))


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "📞 پشتیبانی")
def support_start(message):
    ensure_user(message)
    wait = anti_spam_remaining(message.from_user.id, "support")
    if wait > 0:
        return bot.send_message(message.chat.id, anti_spam_wait_text("support", wait), reply_markup=main_keyboard(message.from_user.id))
    states[str(message.from_user.id)] = {"flow": "support_message", "step": "message"}
    bot.send_message(message.chat.id, "📞 پیام خود را ارسال کنید.", reply_markup=back_cancel_keyboard())


@bot.callback_query_handler(func=lambda c: c.data.startswith("support_warn:"))
def support_warn_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    support_id = call.data.split(":", 1)[1]
    item = get_support_message(support_id)
    if not item:
        return bot.answer_callback_query(call.id, "پیام پیدا نشد.")
    count, blocked = add_user_warning(item.get("user_id"))
    if blocked:
        warning_text = BLOCKED_TEXT
        answer_text = "کاربر با 5 اخطار مسدود شد."
        status = "blocked"
    else:
        warning_text = "⚠️ اخطار %s از 5: لطفاً پیام محترمانه ارسال کنید." % count
        answer_text = "اخطار %s/5 ارسال شد." % count
        status = "warned"
    try:
        bot.send_message(item.get("user_id"), warning_text)
    except Exception as e:
        logging.error("support warning notify user error: %s", e)
    update_support_message(support_id, {
        "status": status,
        "warned_by": str(call.from_user.id),
        "warning_count_after": count,
        "blocked_after_warning": blocked,
    })
    bot.answer_callback_query(call.id, answer_text)
    try:
        bot.edit_message_text(support_admin_text(get_support_message(support_id)), chat_id=call.message.chat.id, message_id=call.message.message_id, parse_mode="HTML", reply_markup=support_admin_markup(support_id))
    except Exception:
        try:
            bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=support_admin_markup(support_id))
        except Exception:
            pass


@bot.callback_query_handler(func=lambda c: c.data.startswith("support_reply:"))
def support_reply_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    support_id = call.data.split(":", 1)[1]
    item = get_support_message(support_id)
    if not item:
        return bot.answer_callback_query(call.id, "پیام پیدا نشد.")
    states[str(call.from_user.id)] = {"flow": "support_reply", "step": "message", "support_id": support_id}
    bot.answer_callback_query(call.id)
    bot.send_message(call.message.chat.id, "✍️ پاسخ خود را ارسال کنید.", reply_markup=back_cancel_keyboard())


@bot.callback_query_handler(func=lambda c: c.data.startswith("u_msg:"))
def user_admin_message_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    target_id = call.data.split(":", 1)[1]
    states[str(call.from_user.id)] = {"flow": "admin_user_message", "step": "message", "target_user_id": target_id}
    bot.answer_callback_query(call.id)
    bot.send_message(call.message.chat.id, "✉️ متن پیام را ارسال کنید.", reply_markup=back_cancel_keyboard())


@bot.callback_query_handler(func=lambda c: c.data.startswith("u_warn:"))
def user_admin_warn_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    target_id = call.data.split(":", 1)[1]
    count, blocked = add_user_warning(target_id)
    try:
        if blocked:
            bot.send_message(target_id, BLOCKED_TEXT)
        else:
            bot.send_message(target_id, "⚠️ اخطار %s از 5 برای شما ثبت شد." % count)
    except Exception as e:
        logging.error("admin user warn notify error: %s", e)
    bot.answer_callback_query(call.id, "اخطار ثبت شد." if not blocked else "کاربر مسدود شد.")
    send_user_admin_panel(call.message.chat.id, target_id, edit_message=call.message)


@bot.callback_query_handler(func=lambda c: c.data.startswith("u_orders:"))
def user_admin_orders_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    target_id = call.data.split(":", 1)[1]
    bot.answer_callback_query(call.id, "تعداد خریدها به‌روزرسانی شد.")
    send_user_admin_panel(call.message.chat.id, target_id, edit_message=call.message)


@bot.callback_query_handler(func=lambda c: c.data.startswith("u_clearwarn:"))
def user_admin_clear_warning_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    target_id = call.data.split(":", 1)[1]
    clear_user_warnings(target_id, call.from_user.id)
    try:
        bot.send_message(target_id, "✅ اخطارهای حساب شما حذف شد.")
    except Exception as e:
        logging.error("admin clear warning notify error: %s", e)
    bot.answer_callback_query(call.id, "اخطارها حذف شد.")
    send_user_admin_panel(call.message.chat.id, target_id, edit_message=call.message)


@bot.callback_query_handler(func=lambda c: c.data.startswith("u_ban:"))
def user_admin_ban_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    target_id = call.data.split(":", 1)[1]
    ban_user(target_id, call.from_user.id)
    try:
        bot.send_message(target_id, "⛔ حساب شما توسط مدیریت مسدود شد.")
    except Exception as e:
        logging.error("admin ban notify error: %s", e)
    bot.answer_callback_query(call.id, "کاربر بن شد.")
    send_user_admin_panel(call.message.chat.id, target_id, edit_message=call.message)


@bot.callback_query_handler(func=lambda c: c.data.startswith("u_unban:"))
def user_admin_unban_callback(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    target_id = call.data.split(":", 1)[1]
    unban_user(target_id, call.from_user.id)
    try:
        bot.send_message(target_id, "✅ حساب شما رفع مسدودی شد.", reply_markup=main_keyboard(int(target_id)))
    except Exception as e:
        logging.error("admin unban notify error: %s", e)
    bot.answer_callback_query(call.id, "کاربر از بن خارج شد.")
    send_user_admin_panel(call.message.chat.id, target_id, edit_message=call.message)


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "⚙️ پنل ادمین")
def admin_panel(message):
    ensure_user(message)
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    bot.send_message(message.chat.id, "⚙️ پنل ادمین", reply_markup=admin_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "➕ افزودن محصول")
def add_product_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    states[str(message.from_user.id)] = {"flow": "add_product", "step": "category", "data": {}}
    send_add_product_step_prompt(message.chat.id, states[str(message.from_user.id)])


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "📦 مدیریت محصولات")
def admin_manage_products(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    send_admin_products_list(message.chat.id)


@bot.message_handler(func=lambda m: getattr(m, "text", None) in ["🧾 سفارش‌ها", "🧾 سفارش‌های در انتظار"])
def admin_pending_orders(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    markup, pending = pending_orders_keyboard()
    if not pending:
        return bot.send_message(message.chat.id, "🧾 سفارش در انتظار ندارید.", reply_markup=admin_keyboard())
    bot.send_message(message.chat.id, "🧾 سفارش‌های در انتظار", reply_markup=markup)


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "📊 آمار")
def admin_stats(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    products = get_products(False)
    orders = list(get_orders().values())
    users = load_json(USERS_FILE, {})
    cfg = reload_config()
    text = "📊 آمار ربات\n\n"
    text += "👥 کاربران: %s\n" % len(users)
    text += "📦 محصولات: %s\n" % len(products)
    text += "🧾 کل سفارش‌ها: %s\n" % len(orders)
    text += "⏳ در انتظار: %s\n" % sum(1 for o in orders if o.get("status") == "pending")
    text += "✅ تایید شده: %s\n" % sum(1 for o in orders if o.get("status") in {"accepted", "preparing", "posted", "delivered"})
    text += "📦 آماده‌سازی: %s\n" % sum(1 for o in orders if o.get("status") == "preparing")
    text += "🚚 تحویل پست: %s\n" % sum(1 for o in orders if o.get("status") == "posted")
    text += "🏁 تحویل مشتری: %s\n" % sum(1 for o in orders if o.get("status") == "delivered")
    text += "❌ رد شده: %s\n\n" % sum(1 for o in orders if o.get("status") == "rejected")
    text += "🌺 قیمت‌های فعلی هر مثقال زعفران:\n%s" % saffron_prices_summary_lines()
    bot.send_message(message.chat.id, text, reply_markup=admin_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "⚙️ تنظیمات")
def admin_settings(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    bot.send_message(message.chat.id, "⚙️ بخش تنظیمات را انتخاب کنید.", reply_markup=settings_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🌺 تنظیمات زعفران")
def admin_saffron_settings(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    text = "🌺 تنظیمات زعفران\n" + saffron_prices_summary_lines()
    bot.send_message(message.chat.id, text, reply_markup=saffron_settings_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "💳 تنظیمات پرداخت")
def admin_payment_settings(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    cfg = reload_config()
    text = "💳 تنظیمات پرداخت | کارت: %s" % (cfg.get("bank_card_number") or "تنظیم نشده")
    bot.send_message(message.chat.id, text, reply_markup=payment_settings_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🏪 تنظیمات فروشگاه")
def admin_store_settings(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    cfg = reload_config()
    text = "🏪 تنظیمات فروشگاه | %s" % cfg.get("store_name", "فروشگاه")
    bot.send_message(message.chat.id, text, reply_markup=store_settings_keyboard())

@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🆔 راهنمای chat_id")
def chatid_help(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    text = "🆔 ربات را ادمین گروه کنید و /chatid بزنید."
    bot.send_message(message.chat.id, text, reply_markup=settings_keyboard())


def start_config_state(message, step, prompt):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    states[str(message.from_user.id)] = {"flow": "config", "step": step}
    bot.send_message(message.chat.id, prompt, reply_markup=back_cancel_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🌺 تنظیم قیمت هر مثقال")
def set_mesghal_price_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    text = "🌺 نوع زعفران را برای قیمت مثقال انتخاب کنید.\n" + saffron_prices_summary_lines()
    bot.send_message(message.chat.id, text, reply_markup=saffron_price_type_keyboard())


@bot.message_handler(func=lambda m: get_saffron_type_key_from_price_button(getattr(m, "text", None)) is not None)
def set_specific_mesghal_price_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    saffron_type_key = get_saffron_type_key_from_price_button(message.text)
    label = get_saffron_type_label(saffron_type_key)
    start_config_state(
        message,
        "saffron_mesghal_price:%s" % saffron_type_key,
        "🌺 قیمت مثقال «%s» را عددی وارد کنید." % label,
    )


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🚚 هزینه ارسال وزن‌ها")
def set_saffron_shipping_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    text = "🚚 نوع زعفران را برای هزینه ارسال انتخاب کنید.\n" + saffron_shipping_all_types_summary_lines()
    bot.send_message(message.chat.id, text, reply_markup=saffron_shipping_type_keyboard())


@bot.message_handler(func=lambda m: get_saffron_type_key_from_shipping_button(getattr(m, "text", None)) is not None)
def set_specific_saffron_shipping_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    saffron_type_key = get_saffron_type_key_from_shipping_button(message.text)
    start_config_state(
        message,
        "saffron_option_shipping_fee:%s" % saffron_type_key,
        saffron_shipping_prompt(saffron_type_key),
    )


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🧾 تنظیم گروه‌های اختصاصی زعفران")
def set_saffron_groups_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    text = "🧾 نوع زعفران را برای گروه سفارش انتخاب کنید.\n" + saffron_groups_summary_lines()
    bot.send_message(message.chat.id, text, reply_markup=saffron_group_keyboard())


@bot.message_handler(func=lambda m: get_saffron_type_key_from_group_button(getattr(m, "text", None)) is not None)
def set_specific_saffron_group_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    saffron_type_key = get_saffron_type_key_from_group_button(message.text)
    label = get_saffron_type_label(saffron_type_key)
    config_name = get_saffron_group_config_name(saffron_type_key)
    start_config_state(
        message,
        "saffron_type_chat_id:%s" % saffron_type_key,
        "🧾 chat_id گروه «%s» را وارد کنید." % label,
    )


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "💳 تنظیم شماره کارت")
def set_bank_card_start(message):
    start_config_state(message, "bank_card_number", "💳 شماره کارت را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "👤 تنظیم صاحب کارت")
def set_bank_holder_start(message):
    start_config_state(message, "bank_card_holder", "👤 نام صاحب کارت را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🏛 تنظیم نام بانک")
def set_bank_name_start(message):
    start_config_state(message, "bank_name", "🏛 نام بانک را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "💳 تنظیم گروه تراکنش")
def set_transaction_group_start(message):
    start_config_state(message, "transaction_chat_id", "💳 chat_id گروه تراکنش را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🧾 تنظیم گروه پیش‌فرض سفارشات")
def set_fallback_group_start(message):
    start_config_state(message, "fallback_order_chat_id", "🧾 chat_id گروه پیش‌فرض را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🏪 تنظیم نام فروشگاه")
def set_store_name_start(message):
    start_config_state(message, "store_name", "🏪 نام فروشگاه را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "💱 تنظیم واحد پول")
def set_currency_start(message):
    start_config_state(message, "currency", "💱 واحد پول را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🏦 تنظیم متن پرداخت/رسید")
def set_payment_text_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    states[str(message.from_user.id)] = {"flow": "setting", "step": "payment_text"}
    bot.send_message(message.chat.id, "🏦 متن پرداخت را وارد کنید.", reply_markup=back_cancel_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "📘 تنظیم راهنما")
def set_help_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    states[str(message.from_user.id)] = {"flow": "setting", "step": "help_text", "data": {}}
    bot.send_message(message.chat.id, "📘 متن راهنما را وارد کنید.", reply_markup=back_cancel_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🌐 تنظیم وبسایت")
def set_website_start(message):
    start_config_state(message, "website_url", "🌐 آدرس وبسایت را وارد کنید.")


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🏷 تنظیم عنوان دسته‌ها")
def set_category_titles_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    text = "🏷 عنوان دسته‌ها\nزعفران: %s\nزرشک: %s" % (get_product_category_label("saffron"), get_product_category_label("barberry"))
    bot.send_message(message.chat.id, text, reply_markup=category_title_settings_keyboard())


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "🚚 هزینه ارسال محصولات")
def set_product_shipping_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    bot.send_message(message.chat.id, "🚚 محصول را برای تنظیم هزینه ارسال انتخاب کنید.", reply_markup=product_shipping_settings_markup())


@bot.message_handler(func=lambda m: getattr(m, "text", None) in ["عنوان زعفران", "عنوان زرشک"])
def set_specific_category_title_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    key = "saffron" if message.text == "عنوان زعفران" else "barberry"
    title = "زعفران" if key == "saffron" else "زرشک"
    start_config_state(message, "category_title:%s" % key, "🏷 عنوان %s را وارد کنید." % title)


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "📢 جوین اجباری")
def admin_force_join_channels(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    send_force_channels_panel(message.chat.id)


@bot.message_handler(func=lambda m: getattr(m, "text", None) == "📣 پیام همگانی")
def broadcast_start(message):
    if not is_admin(message.from_user.id):
        return bot.send_message(message.chat.id, "⛔ دسترسی ندارید.")
    states[str(message.from_user.id)] = {"flow": "broadcast", "step": "text"}
    bot.send_message(message.chat.id, "📣 متن پیام را وارد کنید.", reply_markup=back_cancel_keyboard())


@bot.callback_query_handler(func=lambda c: c.data == "force_channels_panel")
def callback_force_channels_panel(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    bot.answer_callback_query(call.id)
    send_force_channels_panel(call.message.chat.id, edit_message=call.message)


@bot.callback_query_handler(func=lambda c: c.data.startswith("force_channel_view:"))
def callback_force_channel_view(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    channel_id = call.data.split(":", 1)[1]
    channel = next((x for x in get_force_channels() if str(x.get("id")) == channel_id), None)
    if not channel:
        return bot.answer_callback_query(call.id, "کانال پیدا نشد.")
    kb = types.InlineKeyboardMarkup()
    kb.add(types.InlineKeyboardButton("🗑 حذف کانال", callback_data="force_channel_delete:%s" % channel_id))
    kb.add(types.InlineKeyboardButton("⬅️ بازگشت", callback_data="force_channels_panel"))
    try:
        bot.edit_message_text(force_channel_detail_text(channel), chat_id=call.message.chat.id, message_id=call.message.message_id, parse_mode="HTML", reply_markup=kb)
    except Exception:
        bot.send_message(call.message.chat.id, force_channel_detail_text(channel), parse_mode="HTML", reply_markup=kb)
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda c: c.data.startswith("force_channel_delete:"))
def callback_force_channel_delete(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    channel_id = call.data.split(":", 1)[1]
    channels = [x for x in get_force_channels() if str(x.get("id")) != channel_id]
    save_force_channels(channels)
    bot.answer_callback_query(call.id, "حذف شد.")
    send_force_channels_panel(call.message.chat.id, edit_message=call.message)


@bot.callback_query_handler(func=lambda c: c.data == "force_channel_add")
def callback_force_channel_add(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    states[str(call.from_user.id)] = {"flow": "force_channel", "step": "name", "data": {}}
    bot.answer_callback_query(call.id)
    try:
        bot.delete_message(call.message.chat.id, call.message.message_id)
    except Exception:
        pass
    bot.send_message(call.message.chat.id, "📢 نام کانال را وارد کنید.", reply_markup=back_cancel_keyboard())


@bot.callback_query_handler(func=lambda c: c.data == "product_categories")
def callback_product_categories(call):
    bot.answer_callback_query(call.id)
    send_product_categories(call.message.chat.id, call=call)


@bot.callback_query_handler(func=lambda c: c.data.startswith("product_category:"))
def callback_product_category(call):
    category_key = call.data.split(":", 1)[1]
    if not get_product_category_by_key(category_key):
        return bot.answer_callback_query(call.id, "دسته‌بندی نامعتبر است.")
    bot.answer_callback_query(call.id)
    send_products_title_list(call.message.chat.id, category_key, call=call)


@bot.callback_query_handler(func=lambda c: c.data.startswith("products_list"))
def callback_products_list(call):
    parts = call.data.split(":", 1)
    category_key = parts[1] if len(parts) > 1 else None
    bot.answer_callback_query(call.id)
    send_products_title_list(call.message.chat.id, category_key, call=call)


@bot.callback_query_handler(func=lambda c: c.data.startswith("view_product:"))
def callback_view_product(call):
    pid = call.data.split(":", 1)[1]
    product = get_product(pid)
    if not product or not product.get("active", True):
        return bot.answer_callback_query(call.id, "این محصول در حال حاضر فعال نیست.")
    bot.answer_callback_query(call.id)

    delete_inline_message(call)
    send_product_card(call.message.chat.id, product, admin=False, back_to_products=True)


@bot.callback_query_handler(func=lambda c: c.data.startswith("buy:"))
def buy_start(call):
    pid = call.data.split(":", 1)[1]
    product = get_product(pid)
    if not product or not product.get("active", True):
        return bot.answer_callback_query(call.id, "این محصول در حال حاضر فعال نیست.")
    uid = str(call.from_user.id)
    wait = anti_spam_remaining(uid, "order")
    if wait > 0:
        return bot.answer_callback_query(call.id, anti_spam_wait_text("order", wait), show_alert=True)
    first_step = "saffron_option" if product.get("pricing_type") == "saffron_options" else "amount"
    states[uid] = {"flow": "order", "step": first_step, "product_id": pid, "data": {}}
    bot.answer_callback_query(call.id)
    send_order_step_prompt(call.message.chat.id, states[uid])


@bot.callback_query_handler(func=lambda c: c.data.startswith("adm_prod:"))
def admin_product_panel(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    pid = call.data.split(":", 1)[1]
    product = get_product(pid)
    if not product:
        return bot.answer_callback_query(call.id, "⚠️ محصول پیدا نشد.")
    markup = types.InlineKeyboardMarkup(row_width=2)
    markup.row(
        types.InlineKeyboardButton("✏️ عنوان", callback_data="prod_edit:title:%s" % pid),
        types.InlineKeyboardButton("🏷 دسته‌بندی", callback_data="prod_edit:category:%s" % pid),
    )
    if product.get("pricing_type") == "saffron_gram":
        price_button = types.InlineKeyboardButton("⚖️ وزن گرم", callback_data="prod_edit:weight_grams:%s" % pid)
    elif product.get("pricing_type") == "saffron_options":
        price_button = types.InlineKeyboardButton("🌺 نوع زعفران", callback_data="prod_edit:saffron_type:%s" % pid)
    else:
        price_button = types.InlineKeyboardButton("💰 قیمت", callback_data="prod_edit:price:%s" % pid)
    markup.row(price_button, types.InlineKeyboardButton("📝 توضیحات", callback_data="prod_edit:description:%s" % pid))
    if product.get("active", True):
        status_button = types.InlineKeyboardButton("⛔ غیرفعال", callback_data="prod_toggle:%s" % pid)
    else:
        status_button = types.InlineKeyboardButton("✅ فعال", callback_data="prod_toggle:%s" % pid)
    markup.row(types.InlineKeyboardButton("🖼 عکس", callback_data="prod_edit:photo:%s" % pid), status_button)
    if product.get("pricing_type") == "saffron_options":
        shipping_label = "🚚 هزینه ارسال وزن‌ها"
    else:
        shipping_label = "🚚 هزینه ارسال محصول"
    markup.row(types.InlineKeyboardButton(shipping_label, callback_data="prod_edit:shipping_fee:%s" % pid))
    markup.row(
        types.InlineKeyboardButton("🗑 حذف", callback_data="prod_delete:%s" % pid),
        types.InlineKeyboardButton("⬅️ لیست محصولات", callback_data="admin_products_list"),
    )
    bot.answer_callback_query(call.id)
    bot.send_message(call.message.chat.id, product_caption(product, admin=True), parse_mode="HTML", reply_markup=markup)


@bot.callback_query_handler(func=lambda c: c.data == "admin_products_list")
def callback_admin_products_list(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    bot.answer_callback_query(call.id)
    send_admin_products_list(call.message.chat.id)


@bot.callback_query_handler(func=lambda c: c.data.startswith("prod_toggle:"))
def admin_product_toggle(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    pid = call.data.split(":", 1)[1]
    product = get_product(pid)
    if not product:
        return bot.answer_callback_query(call.id, "⚠️ محصول پیدا نشد.")
    new_active = not product.get("active", True)
    update_product(pid, {"active": new_active, "updated_at": now_str()})
    bot.answer_callback_query(call.id, "انجام شد.")
    bot.send_message(call.message.chat.id, "✅ وضعیت محصول تغییر کرد.", reply_markup=admin_keyboard())
    send_admin_products_list(call.message.chat.id)


@bot.callback_query_handler(func=lambda c: c.data.startswith("prod_delete:"))
def admin_product_delete_confirm(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    pid = call.data.split(":", 1)[1]
    markup = types.InlineKeyboardMarkup()
    markup.add(
        types.InlineKeyboardButton("بله، حذف شود", callback_data="prod_delete_yes:%s" % pid),
        types.InlineKeyboardButton("خیر", callback_data="noop"),
    )
    bot.answer_callback_query(call.id)
    bot.send_message(call.message.chat.id, "🗑 حذف محصول؟", reply_markup=markup)


@bot.callback_query_handler(func=lambda c: c.data.startswith("prod_delete_yes:"))
def admin_product_delete(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    pid = call.data.split(":", 1)[1]
    deleted = delete_product(pid)
    bot.answer_callback_query(call.id, "حذف شد." if deleted else "پیدا نشد.")
    bot.send_message(call.message.chat.id, "✅ محصول حذف شد." if deleted else "⚠️ محصول پیدا نشد.", reply_markup=admin_keyboard())
    if deleted:
        send_admin_products_list(call.message.chat.id)


@bot.callback_query_handler(func=lambda c: c.data.startswith("prod_edit:"))
def admin_product_edit_start(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    _, field, pid = call.data.split(":", 2)
    product = get_product(pid)
    if not product:
        return bot.answer_callback_query(call.id, "⚠️ محصول پیدا نشد.")
    uid = str(call.from_user.id)
    if field == "shipping_fee" and product.get("pricing_type") == "saffron_options":
        saffron_type_key = product.get("saffron_type", "sargol")
        states[uid] = {"flow": "config", "step": "saffron_option_shipping_fee:%s" % saffron_type_key}
        bot.answer_callback_query(call.id)
        return bot.send_message(call.message.chat.id, saffron_shipping_prompt(saffron_type_key), reply_markup=back_cancel_keyboard())
    states[uid] = {"flow": "edit_product", "step": field, "product_id": pid}
    prompts = {
        "title": "📦 عنوان جدید را وارد کنید.",
        "category": "🏷 دسته‌بندی جدید را انتخاب کنید.",
        "price": "💰 قیمت جدید را وارد کنید.",
        "weight_grams": "⚖️ وزن جدید را وارد کنید.",
        "saffron_type": "🌺 نوع جدید را انتخاب کنید.",
        "description": "📝 توضیحات جدید را وارد کنید.",
        "photo": "📷 عکس جدید را ارسال کنید.",
        "shipping_fee": "🚚 هزینه ارسال را عددی وارد کنید. برای ارسال رایگان عدد 0 را بزنید.",
    }
    bot.answer_callback_query(call.id)
    if field == "saffron_type":
        reply_markup = saffron_type_keyboard()
    elif field == "category":
        reply_markup = product_category_keyboard()
    else:
        reply_markup = back_cancel_keyboard()
    bot.send_message(call.message.chat.id, prompts.get(field, "مقدار جدید را وارد کنید."), reply_markup=reply_markup)


@bot.callback_query_handler(func=lambda c: c.data.startswith("view_order:"))
def admin_view_order(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "دسترسی ندارید.")
    order_id = call.data.split(":", 1)[1]
    order = get_order(order_id)
    if not order:
        return bot.answer_callback_query(call.id, "سفارش پیدا نشد.")
    markup = order_admin_markup(order)
    bot.answer_callback_query(call.id)
    send_receipt_to_chat(call.message.chat.id, order)
    bot.send_message(call.message.chat.id, order_text(order), parse_mode="HTML", reply_markup=markup)

@bot.callback_query_handler(func=lambda c: c.data.startswith("order_ok:") or c.data.startswith("order_no:"))
def order_action(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "فقط ادمین مجاز است.")
    action, order_id = call.data.split(":", 1)
    order = get_order(order_id)
    if not order:
        return bot.answer_callback_query(call.id, "سفارش پیدا نشد.")
    if order.get("status") != "pending":
        return bot.answer_callback_query(call.id, "این سفارش قبلاً بررسی شده است.")

    if action == "order_ok":
        updates = {
            "status": "accepted",
            "tracking_code": generate_tracking_code(),
            "tracking_created_at": now_str(),
            "updated_at": now_str(),
            "action_by": str(call.from_user.id),
            "action_by_label": get_user_label(call.from_user.id),
        }
        admin_status = "تأیید شده"
        answer = "سفارش تایید شد."
    else:
        updates = {
            "status": "rejected",
            "updated_at": now_str(),
            "action_by": str(call.from_user.id),
            "action_by_label": get_user_label(call.from_user.id),
        }
        admin_status = "رد شده"
        answer = "سفارش رد شد."

    order = update_order(order_id, updates)

    try:
        final_text = order_text(order, status_text=admin_status)
        final_text += "\n👨‍💼 بررسی توسط: %s" % html_escape(get_user_label(call.from_user.id))
        bot.edit_message_text(final_text, chat_id=call.message.chat.id, message_id=call.message.message_id, parse_mode="HTML", reply_markup=order_admin_markup(order))
    except Exception as e:
        logging.error("edit order message error: %s", e)

    if order.get("status") == "accepted":
        try:
            bot.send_message(
                order.get("user_id"),
                "✅ سفارش شما تایید شد.\n🔎 کد پیگیری: <code>%s</code>" % html_escape(order.get("tracking_code")),
                parse_mode="HTML",
                reply_markup=main_keyboard(int(order.get("user_id"))),
            )
        except Exception as e:
            logging.error("notify accepted order error: %s", e)
    else:
        notify_order_status(order)

    bot.answer_callback_query(call.id, answer)


@bot.callback_query_handler(func=lambda c: c.data.startswith("order_stage:"))
def order_stage_action(call):
    if not is_admin(call.from_user.id):
        return bot.answer_callback_query(call.id, "فقط ادمین مجاز است.")
    parts = call.data.split(":", 2)
    if len(parts) != 3:
        return bot.answer_callback_query(call.id, "دستور نامعتبر است.")
    _, new_status, order_id = parts
    order = get_order(order_id)
    if not order:
        return bot.answer_callback_query(call.id, "سفارش پیدا نشد.")
    transitions = {"accepted": "preparing", "preparing": "posted", "posted": "delivered"}
    current = order.get("status")
    if transitions.get(current) != new_status:
        return bot.answer_callback_query(call.id, "مرحله سفارش معتبر نیست.")
    order = ensure_tracking_code(order_id)
    order = update_order(order_id, {
        "status": new_status,
        "updated_at": now_str(),
        "stage_by": str(call.from_user.id),
        "stage_by_label": get_user_label(call.from_user.id),
        "stage_%s_at" % new_status: now_str(),
    })
    try:
        final_text = order_text(order)
        final_text += "\n👨‍💼 بروزرسانی توسط: %s" % html_escape(get_user_label(call.from_user.id))
        bot.edit_message_text(final_text, chat_id=call.message.chat.id, message_id=call.message.message_id, parse_mode="HTML", reply_markup=order_admin_markup(order))
    except Exception as e:
        logging.error("edit order stage message error: %s", e)
    notify_order_status(order)
    bot.answer_callback_query(call.id, "وضعیت سفارش بروزرسانی شد.")

@bot.callback_query_handler(func=lambda c: c.data == "noop")
def noop(call):
    bot.answer_callback_query(call.id)


@bot.message_handler(content_types=["text", "photo", "document"], func=lambda m: str(m.from_user.id) in states)
def state_router(message):
    uid = str(message.from_user.id)
    state = states.get(uid)
    if not state:
        return
    if message.text == CANCEL_TEXT:
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "❌ عملیات لغو شد.", reply_markup=main_keyboard(message.from_user.id))
    if message.text == BACK_TEXT:
        return handle_state_back(message, state)

    flow = state.get("flow")
    if flow == "add_product":
        return handle_add_product_state(message, state)
    if flow == "edit_product":
        return handle_edit_product_state(message, state)
    if flow == "order":
        return handle_order_state(message, state)
    if flow == "setting":
        return handle_setting_state(message, state)
    if flow == "config":
        return handle_config_state(message, state)
    if flow == "broadcast":
        return handle_broadcast_state(message, state)
    if flow == "support_message":
        return handle_support_message_state(message, state)
    if flow == "support_reply":
        return handle_support_reply_state(message, state)
    if flow == "track_order":
        return handle_track_order_state(message, state)
    if flow == "force_channel":
        return handle_force_channel_state(message, state)
    if flow == "admin_user_message":
        return handle_admin_user_message_state(message, state)


def handle_force_channel_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    step = state.get("step")
    data = state.setdefault("data", {})
    if step == "name":
        name = (message.text or "").strip()
        if not name:
            return bot.send_message(message.chat.id, "📢 نام کانال را وارد کنید.", reply_markup=back_cancel_keyboard())
        data["name"] = name
        state["step"] = "chat_id"
        return bot.send_message(message.chat.id, "🆔 آیدی عددی یا یوزرنیم کانال را وارد کنید.", reply_markup=back_cancel_keyboard())
    if step == "chat_id":
        resolved, error = resolve_force_channel(message.text or "")
        if not resolved:
            return bot.send_message(message.chat.id, "⚠️ %s" % error, reply_markup=back_cancel_keyboard())
        channels = get_force_channels()
        if any(str(x.get("chat_id")) == str(resolved.get("chat_id")) for x in channels):
            return bot.send_message(message.chat.id, "⚠️ این کانال قبلاً ثبت شده است.", reply_markup=back_cancel_keyboard())
        channel = {
            "id": uuid.uuid4().hex[:10],
            "name": data.get("name"),
            "chat_id": resolved.get("chat_id"),
            "username": resolved.get("username", ""),
            "link": resolved.get("link", ""),
            "successful_joins": 0,
            "created_at": now_str(),
        }
        channels.append(channel)
        save_force_channels(channels)
        states.pop(uid, None)
        bot.send_message(message.chat.id, "✅ کانال جوین اجباری اضافه شد.", reply_markup=store_settings_keyboard())
        return send_force_channels_panel(message.chat.id)
    states.pop(uid, None)
    return bot.send_message(message.chat.id, "⚠️ مرحله نامعتبر است.", reply_markup=store_settings_keyboard())


def handle_add_product_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")

    step = state.get("step")
    data = state.setdefault("data", {})

    if step == "category":
        item = get_product_category_by_button(message.text or "")
        if not item:
            return bot.send_message(message.chat.id, "🏷 دسته‌بندی را انتخاب کنید.", reply_markup=product_category_keyboard())
        data["category"] = item["key"]
        state["step"] = "title"
        return send_add_product_step_prompt(message.chat.id, state)

    if step == "title":
        title = (message.text or "").strip()
        if not title:
            return bot.send_message(message.chat.id, "📦 عنوان معتبر وارد کنید.", reply_markup=back_cancel_keyboard())
        data["title"] = title
        if data.get("category") == "barberry":
            data["pricing_type"] = "manual"
            data["weight_grams"] = ""
            data["saffron_type"] = ""
            state["step"] = "price"
        else:
            state["step"] = "pricing_mode"
        return send_add_product_step_prompt(message.chat.id, state)

    if step == "pricing_mode":
        text = (message.text or "").strip()
        if text in ["🌺 زعفران با گزینه‌های آماده", "🌺 زعفران بر اساس گرم"]:
            data["pricing_type"] = "saffron_options"
            data["weight_grams"] = ""
            data["price"] = "0"
            state["step"] = "saffron_type"
            return send_add_product_step_prompt(message.chat.id, state)
        if text == "💰 قیمت دستی":
            data["pricing_type"] = "manual"
            state["step"] = "price"
            return send_add_product_step_prompt(message.chat.id, state)
        return bot.send_message(message.chat.id, "💰 نوع قیمت‌گذاری را انتخاب کنید.", reply_markup=pricing_keyboard())

    if step == "saffron_type":
        item = get_saffron_type_by_label(message.text or "")
        if not item:
            return bot.send_message(message.chat.id, "🌺 نوع زعفران را انتخاب کنید.", reply_markup=saffron_type_keyboard())
        data["saffron_type"] = item["key"]
        state["step"] = "description"
        bot.send_message(
            message.chat.id,
            "🌺 نوع زعفران ثبت شد: %s" % item["label"],
        )
        return send_add_product_step_prompt(message.chat.id, state)

    if step == "weight_grams":
        weight = parse_float(message.text or "")
        if weight is None or weight <= 0:
            return bot.send_message(message.chat.id, "⚖️ وزن معتبر وارد کنید.", reply_markup=back_cancel_keyboard())
        data["weight_grams"] = str(weight)
        data["price"] = "0"
        state["step"] = "description"
        preview = product_unit_price({"pricing_type": "saffron_gram", "weight_grams": weight})
        bot.send_message(message.chat.id, "💰 قیمت فعلی: %s %s" % (fmt_price(preview), get_currency()))
        return send_add_product_step_prompt(message.chat.id, state)

    if step == "price":
        price = parse_price(message.text or "")
        if price is None:
            return bot.send_message(message.chat.id, "💰 قیمت را عددی وارد کنید.", reply_markup=back_cancel_keyboard())
        data["price"] = str(price)
        state["step"] = "description"
        return send_add_product_step_prompt(message.chat.id, state)

    if step == "description":
        desc = (message.text or "").strip()
        if not desc:
            return bot.send_message(message.chat.id, "📝 توضیحات را وارد کنید.", reply_markup=back_cancel_keyboard())
        data["description"] = desc
        state["step"] = "photo"
        return send_add_product_step_prompt(message.chat.id, state)

    if step == "photo":
        if message.content_type == "photo":
            data["photo_file_id"] = message.photo[-1].file_id
        elif message.text == SKIP_TEXT:
            data["photo_file_id"] = ""
        else:
            return bot.send_message(message.chat.id, "📷 عکس را ارسال کنید.", reply_markup=optional_back_cancel_keyboard(SKIP_TEXT))

        product = {
            "id": uuid.uuid4().hex[:10],
            "category": data.get("category", "saffron" if data.get("pricing_type") in ["saffron_options", "saffron_gram"] else "barberry"),
            "title": data.get("title"),
            "pricing_type": data.get("pricing_type", "manual"),
            "weight_grams": data.get("weight_grams", ""),
            "price": data.get("price", "0"),
            "saffron_type": data.get("saffron_type", ""),
            "description": data.get("description", ""),
            "photo_file_id": data.get("photo_file_id", ""),
            "order_chat_id": "",
            "shipping_fee": "0",
            "active": True,
            "created_at": now_str(),
            "updated_at": now_str(),
        }
        products = get_products(False)
        products.append(product)
        save_products(products)
        states.pop(uid, None)
        bot.send_message(message.chat.id, "✅ محصول اضافه شد.", reply_markup=admin_keyboard())
        return send_product_card(message.chat.id, product, admin=True)


def handle_edit_product_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    pid = state.get("product_id")
    field = state.get("step")
    product = get_product(pid)
    if not product:
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚠️ محصول پیدا نشد.", reply_markup=admin_keyboard())

    updates = {"updated_at": now_str()}
    if field == "photo":
        if message.content_type == "photo":
            updates["photo_file_id"] = message.photo[-1].file_id
        elif (message.text or "").strip() == DELETE_PHOTO_TEXT:
            updates["photo_file_id"] = ""
        else:
            return bot.send_message(message.chat.id, "📷 عکس جدید را ارسال کنید.", reply_markup=back_cancel_keyboard())
    elif field == "category":
        item = get_product_category_by_button(message.text or "")
        if not item:
            return bot.send_message(message.chat.id, "🏷 دسته‌بندی را انتخاب کنید.", reply_markup=product_category_keyboard())
        updates["category"] = item["key"]
        if item["key"] == "barberry" and product.get("pricing_type") in ["saffron_options", "saffron_gram"]:

            updates["pricing_type"] = "manual"
            updates["saffron_type"] = ""
            updates["weight_grams"] = ""
    elif field == "saffron_type":
        item = get_saffron_type_by_label(message.text or "")
        if not item:
            return bot.send_message(message.chat.id, "🌺 نوع زعفران را انتخاب کنید.", reply_markup=saffron_type_keyboard())
        updates["saffron_type"] = item["key"]
        updates["category"] = "saffron"
    elif field == "price":
        value = parse_price(message.text or "")
        if value is None:
            return bot.send_message(message.chat.id, "💰 قیمت را عددی وارد کنید.", reply_markup=back_cancel_keyboard())
        updates["price"] = str(value)
    elif field == "shipping_fee":
        value = parse_price(message.text or "")
        if value is None or value < 0:
            return bot.send_message(message.chat.id, "🚚 هزینه ارسال را عددی وارد کنید.", reply_markup=back_cancel_keyboard())
        updates["shipping_fee"] = str(value)
    elif field == "weight_grams":
        value = parse_float(message.text or "")
        if value is None or value <= 0:
            return bot.send_message(message.chat.id, "⚖️ وزن را عددی وارد کنید.", reply_markup=back_cancel_keyboard())
        updates["weight_grams"] = str(value)
    elif field in ["title", "description"]:
        value = (message.text or "").strip()
        if not value:
            return bot.send_message(message.chat.id, "⚠️ مقدار معتبر نیست.", reply_markup=back_cancel_keyboard())
        updates[field] = value
    else:
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚠️ فیلد نامعتبر است.", reply_markup=admin_keyboard())

    product = update_product(pid, updates)
    states.pop(uid, None)
    bot.send_message(message.chat.id, "✅ محصول ویرایش شد.", reply_markup=admin_keyboard())
    return send_product_card(message.chat.id, product, admin=True)


def handle_order_state(message, state):
    uid = str(message.from_user.id)
    ensure_user(message)
    pid = state.get("product_id")
    product = get_product(pid)
    if not product or not product.get("active", True):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚠️ محصول قابل سفارش نیست.", reply_markup=main_keyboard(message.from_user.id))

    step = state.get("step")
    data = state.setdefault("data", {})

    if step == "saffron_option":
        opt = get_saffron_option_by_label(message.text or "")
        if not opt:
            return bot.send_message(message.chat.id, "🌺 مقدار زعفران را انتخاب کنید.", reply_markup=saffron_options_keyboard())
        saffron_type = product.get("saffron_type", "sargol")
        if get_saffron_mesghal_price(saffron_type) <= 0:
            return bot.send_message(message.chat.id, "⚠️ قیمت روز تنظیم نشده است.", reply_markup=main_keyboard(message.from_user.id))
        data["saffron_option_key"] = opt["key"]
        data["saffron_option_label"] = opt["label"]
        data["saffron_option_grams"] = str(opt["grams"])
        data["amount"] = opt["label"]
        state["step"] = "quantity"
        return send_order_step_prompt(message.chat.id, state)

    if step == "quantity":
        qty = parse_order_count(message.text or "")
        if qty is None:
            return bot.send_message(message.chat.id, "🔢 تعداد معتبر وارد کنید.", reply_markup=quantity_keyboard())
        data["quantity"] = str(qty)
        state["step"] = "full_name"
        return send_order_step_prompt(message.chat.id, state)

    if step == "amount":
        value = (message.text or "").strip()
        if not value or value in [NO_NOTE_TEXT, SKIP_TEXT]:
            return bot.send_message(message.chat.id, "🔢 تعداد یا مقدار را وارد کنید.", reply_markup=back_cancel_keyboard())
        data["amount"] = value
        state["step"] = "full_name"
        return send_order_step_prompt(message.chat.id, state)

    if step == "full_name":
        value = (message.text or "").strip()
        if len(value) < 2:
            return bot.send_message(message.chat.id, "👤 نام کامل را وارد کنید.", reply_markup=back_cancel_keyboard())
        data["full_name"] = value
        state["step"] = "phone"
        return send_order_step_prompt(message.chat.id, state)

    if step == "phone":
        value = (message.text or "").strip()
        digits = normalize_digits(value).replace("+", "").replace(" ", "").replace("-", "")
        if not digits.isdigit() or len(digits) < 8:
            return bot.send_message(message.chat.id, "📞 شماره تماس معتبر وارد کنید.", reply_markup=back_cancel_keyboard())
        data["phone"] = value
        state["step"] = "address"
        return send_order_step_prompt(message.chat.id, state)

    if step == "address":
        value = (message.text or "").strip()
        if len(value) < 10:
            return bot.send_message(message.chat.id, "📍 آدرس کامل‌تر وارد کنید.", reply_markup=back_cancel_keyboard())
        data["address"] = value
        state["step"] = "postal_code"
        return send_order_step_prompt(message.chat.id, state)

    if step == "postal_code":
        value = normalize_digits((message.text or "").strip()).replace("-", "").replace(" ", "")
        if not value.isdigit() or len(value) != 10:
            return bot.send_message(message.chat.id, "🏷 کد پستی ۱۰ رقمی وارد کنید.", reply_markup=back_cancel_keyboard())
        data["postal_code"] = value
        state["step"] = "note"
        return send_order_step_prompt(message.chat.id, state)

    if step == "note":
        data["note"] = "" if message.text == NO_NOTE_TEXT else (message.text or "").strip()
        state["step"] = "receipt"
        return send_order_step_prompt(message.chat.id, state)

    if step == "receipt":
        receipt_file_id = ""
        receipt_file_type = "photo"
        receipt_caption = ""

        if message.content_type == "photo":
            receipt_file_id = message.photo[-1].file_id
            receipt_file_type = "photo"
            receipt_caption = message.caption or ""
        elif message.content_type == "document":
            receipt_file_id = message.document.file_id
            receipt_file_type = "document"
            receipt_caption = message.caption or ""
        else:
            return bot.send_message(message.chat.id, "🧾 تصویر رسید واریزی را ارسال کنید.", reply_markup=back_cancel_keyboard())

        wait = anti_spam_remaining(uid, "order")
        if wait > 0:
            return bot.send_message(message.chat.id, anti_spam_wait_text("order", wait), reply_markup=back_cancel_keyboard())

        order = create_order(
            user_id=message.from_user.id,
            username=message.from_user.username,
            product=product,
            amount=data.get("amount"),
            full_name=data.get("full_name"),
            phone=data.get("phone"),
            address=data.get("address"),
            postal_code=data.get("postal_code"),
            note=data.get("note", ""),
            receipt_file_id=receipt_file_id,
            receipt_file_type=receipt_file_type,
            receipt_caption=receipt_caption,
            saffron_option_key=data.get("saffron_option_key", ""),
            quantity_count=data.get("quantity", "1"),
        )
        mark_anti_spam_action(uid, "order")
        states.pop(uid, None)
        sent = send_order_to_destination(order)
        text = "✅ رسید دریافت شد. سفارش شما برای بررسی ارسال شد."
        if order.get("total_price"):
            text += "\n💰 مبلغ: %s %s" % (fmt_price(order.get("total_price")), get_currency())
        text += "\n🔎 کد پیگیری پس از تایید سفارش ارسال می‌شود."
        if not sent:
            text += "\n⚠️ ارسال به گروه ناموفق بود."
        return bot.send_message(message.chat.id, text, reply_markup=main_keyboard(message.from_user.id))


def handle_setting_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    step = state.get("step")
    data = state.setdefault("data", {})
    settings = load_json(SETTINGS_FILE, DEFAULT_SETTINGS)

    if step == "payment_text":
        value = (message.text or "").strip()
        if not value:
            return bot.send_message(message.chat.id, "🏦 متن پرداخت را وارد کنید.", reply_markup=back_cancel_keyboard())
        settings["payment_text"] = value
        save_json(SETTINGS_FILE, settings)
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "✅ متن پرداخت ذخیره شد.", reply_markup=payment_settings_keyboard())

    if step == "help_text":
        if message.content_type != "text":
            return bot.send_message(message.chat.id, "📘 متن راهنما را وارد کنید.", reply_markup=back_cancel_keyboard())
        value = (message.text or "").strip()
        if not value:
            return bot.send_message(message.chat.id, "📘 متن راهنما را وارد کنید.", reply_markup=back_cancel_keyboard())
        data["help_text"] = value
        state["step"] = "help_photo"
        return bot.send_message(message.chat.id, "🖼 عکس راهنما را ارسال کنید.", reply_markup=help_photo_keyboard())

    if step == "help_photo":
        settings["help_text"] = data.get("help_text") or settings.get("help_text") or DEFAULT_SETTINGS["help_text"]
        if message.content_type == "photo":
            settings["help_photo_file_id"] = message.photo[-1].file_id
        elif (message.text or "").strip() in [NO_HELP_PHOTO_TEXT, DELETE_PHOTO_TEXT]:
            settings["help_photo_file_id"] = ""
        else:
            return bot.send_message(message.chat.id, "🖼 عکس راهنما را ارسال کنید.", reply_markup=help_photo_keyboard())
        save_json(SETTINGS_FILE, settings)
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "✅ راهنما ذخیره شد.", reply_markup=store_settings_keyboard())

    states.pop(uid, None)
    return bot.send_message(message.chat.id, "⚠️ تنظیم نامعتبر است.", reply_markup=settings_keyboard())


def handle_config_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    step = state.get("step")
    cfg = reload_config()
    value = (message.text or "").strip()

    if step.startswith("saffron_type_chat_id:"):
        saffron_type_key = step.split(":", 1)[1]
        config_name = get_saffron_group_config_name(saffron_type_key)
        if not config_name:
            states.pop(uid, None)
            return bot.send_message(message.chat.id, "⚠️ نوع زعفران نامعتبر است.", reply_markup=settings_keyboard())
        chat_ids = cfg.get("saffron_type_chat_ids")
        if not isinstance(chat_ids, dict):
            chat_ids = {}
        if value == EMPTY_TEXT:
            chat_ids[config_name] = ""
        elif value.lstrip("-").isdigit():
            chat_ids[config_name] = value
        else:
            return bot.send_message(message.chat.id, "🧾 chat_id عددی یا «خالی» ارسال کنید.", reply_markup=back_cancel_keyboard())
        cfg["saffron_type_chat_ids"] = chat_ids
    elif step in ["fallback_order_chat_id", "transaction_chat_id"]:
        if value == EMPTY_TEXT:
            cfg[step] = ""
        elif value.lstrip("-").isdigit():
            cfg[step] = value
        else:
            return bot.send_message(message.chat.id, "🧾 chat_id عددی یا «خالی» ارسال کنید.", reply_markup=back_cancel_keyboard())
    elif step == "bank_card_number":
        if value == EMPTY_TEXT:
            cfg["bank_card_number"] = ""
        else:
            digits = normalize_digits(value).replace(" ", "").replace("-", "")
            if not digits.isdigit() or len(digits) < 12:
                return bot.send_message(message.chat.id, "💳 شماره کارت معتبر وارد کنید.", reply_markup=back_cancel_keyboard())
            cfg["bank_card_number"] = value
    elif step == "bank_card_holder":
        cfg["bank_card_holder"] = "" if value == EMPTY_TEXT else value
    elif step == "bank_name":
        cfg["bank_name"] = "" if value == EMPTY_TEXT else value
    elif step.startswith("saffron_option_shipping_fee:"):
        saffron_type_key = step.split(":", 1)[1]
        if not get_saffron_type_by_key(saffron_type_key):
            states.pop(uid, None)
            return bot.send_message(message.chat.id, "⚠️ نوع زعفران نامعتبر است.", reply_markup=saffron_settings_keyboard())
        fees = parse_saffron_shipping_fees_text(value)
        if fees is None:
            return bot.send_message(message.chat.id, "🚚 یک عدد یا دقیقاً 7 عدد وارد کنید.", reply_markup=back_cancel_keyboard())
        all_fees = cfg.get("saffron_option_shipping_fees")
        if not isinstance(all_fees, dict):
            all_fees = {}
        all_fees[saffron_type_key] = fees
        cfg["saffron_option_shipping_fees"] = all_fees
    elif step.startswith("saffron_mesghal_price"):
        price = parse_price(value)
        if price is None or price <= 0:
            return bot.send_message(message.chat.id, "🌺 قیمت مثقال را عددی وارد کنید.", reply_markup=back_cancel_keyboard())
        prices = cfg.get("saffron_mesghal_prices")
        if not isinstance(prices, dict):
            prices = {}
        if ":" in step:
            saffron_type_key = step.split(":", 1)[1]
            prices[saffron_type_key] = str(price)
            cfg["saffron_mesghal_prices"] = prices
        else:
            cfg["saffron_mesghal_price"] = str(price)
            for item in SAFFRON_TYPES:
                prices[item["key"]] = str(price)
            cfg["saffron_mesghal_prices"] = prices
    elif step.startswith("category_title:"):
        category_key = step.split(":", 1)[1]
        if category_key not in PRODUCT_CATEGORY_KEYS:
            states.pop(uid, None)
            return bot.send_message(message.chat.id, "⚠️ دسته‌بندی نامعتبر است.", reply_markup=store_settings_keyboard())
        if not value:
            return bot.send_message(message.chat.id, "🏷 عنوان دسته را وارد کنید.", reply_markup=back_cancel_keyboard())
        titles = cfg.get("category_titles")
        if not isinstance(titles, dict):
            titles = {}
        titles[category_key] = value
        cfg["category_titles"] = titles
    elif step == "store_name":
        cfg["store_name"] = value or cfg.get("store_name", "فروشگاه")
    elif step == "currency":
        cfg["currency"] = value or "تومان"
    elif step == "website_url":
        if value == EMPTY_TEXT:
            cfg["website_url"] = ""
        else:
            url = value.strip()
            if url and not (url.startswith("http://") or url.startswith("https://")):
                url = "https://" + url
            cfg["website_url"] = url
    else:
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚠️ تنظیم نامعتبر است.", reply_markup=settings_keyboard())

    save_json(CONFIG_FILE, cfg)
    reload_config()
    states.pop(uid, None)
    if step.startswith("saffron_mesghal_price") or step.startswith("saffron_type_chat_id") or step.startswith("saffron_option_shipping_fee"):
        reply_markup = saffron_settings_keyboard()
    elif step in ["bank_card_number", "bank_card_holder", "bank_name", "transaction_chat_id"]:
        reply_markup = payment_settings_keyboard()
    elif step in ["fallback_order_chat_id", "store_name", "currency", "website_url"] or step.startswith("category_title:"):
        reply_markup = store_settings_keyboard()
    else:
        reply_markup = settings_keyboard()
    bot.send_message(message.chat.id, "✅ تنظیمات ذخیره شد.", reply_markup=reply_markup)


def handle_support_message_state(message, state):
    uid = str(message.from_user.id)
    ensure_user(message)
    if message.content_type != "text":
        return bot.send_message(message.chat.id, "📝 لطفاً متن پیام را ارسال کنید.", reply_markup=back_cancel_keyboard())
    text = (message.text or "").strip()
    if not text:
        return bot.send_message(message.chat.id, "📝 لطفاً متن پیام را ارسال کنید.", reply_markup=back_cancel_keyboard())
    wait = anti_spam_remaining(uid, "support")
    if wait > 0:
        states.pop(uid, None)
        return bot.send_message(message.chat.id, anti_spam_wait_text("support", wait), reply_markup=main_keyboard(message.from_user.id))
    item = create_support_message(message, text)
    sent = send_support_to_admins(item)
    states.pop(uid, None)
    if sent:
        mark_anti_spam_action(uid, "support")
        return bot.send_message(message.chat.id, "✅ پیام شما برای پشتیبانی ارسال شد.", reply_markup=main_keyboard(message.from_user.id))
    return bot.send_message(message.chat.id, "⚠️ ارسال پیام ناموفق بود. لطفاً بعداً تلاش کنید.", reply_markup=main_keyboard(message.from_user.id))


def handle_support_reply_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    if message.content_type != "text":
        return bot.send_message(message.chat.id, "✍️ پاسخ را به‌صورت متن ارسال کنید.", reply_markup=back_cancel_keyboard())
    text = (message.text or "").strip()
    if not text:
        return bot.send_message(message.chat.id, "✍️ پاسخ را وارد کنید.", reply_markup=back_cancel_keyboard())
    support_id = state.get("support_id")
    item = get_support_message(support_id)
    if not item:
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚠️ پیام پشتیبانی پیدا نشد.", reply_markup=admin_keyboard())
    try:
        bot.send_message(item.get("user_id"), "📞 پاسخ پشتیبانی:\n%s" % text)
        update_support_message(support_id, {"status": "answered", "answer": text, "answered_by": str(message.from_user.id)})
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "✅ پاسخ ارسال شد.", reply_markup=admin_keyboard())
    except Exception as e:
        logging.error("support reply error: %s", e)
        return bot.send_message(message.chat.id, "⚠️ ارسال پاسخ ناموفق بود.", reply_markup=admin_keyboard())


def handle_admin_user_message_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    if message.content_type != "text":
        return bot.send_message(message.chat.id, "✉️ متن پیام را ارسال کنید.", reply_markup=back_cancel_keyboard())
    text = (message.text or "").strip()
    if not text:
        return bot.send_message(message.chat.id, "✉️ متن پیام را وارد کنید.", reply_markup=back_cancel_keyboard())
    target_id = str(state.get("target_user_id", "")).strip()
    if not target_id:
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "⚠️ کاربر نامعتبر است.", reply_markup=admin_keyboard())
    try:
        bot.send_message(target_id, "📩 پیام مدیریت:\n%s" % text)
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "✅ پیام ارسال شد.", reply_markup=admin_keyboard())
    except Exception as e:
        logging.error("admin user message error: %s", e)
        return bot.send_message(message.chat.id, "⚠️ ارسال پیام ناموفق بود.", reply_markup=admin_keyboard())


def handle_broadcast_state(message, state):
    uid = str(message.from_user.id)
    if not is_admin(message.from_user.id):
        states.pop(uid, None)
        return bot.send_message(message.chat.id, "دسترسی ندارید.")
    text = message.text or ""
    if not text.strip():
        return bot.send_message(message.chat.id, "📣 متن پیام را وارد کنید.", reply_markup=back_cancel_keyboard())
    users = load_json(USERS_FILE, {})
    ok = 0
    fail = 0
    for target_id in list(users.keys()):
        try:
            bot.send_message(target_id, text)
            ok += 1
            time.sleep(0.05)
        except Exception:
            fail += 1
    states.pop(uid, None)
    bot.send_message(message.chat.id, "📣 پیام ارسال شد. موفق: %s | ناموفق: %s" % (ok, fail), reply_markup=admin_keyboard())


@bot.message_handler(func=lambda m: True, content_types=["text"])
def fallback(message):
    ensure_user(message)
    if message.chat.type != "private":
        return
    bot.send_message(message.chat.id, "🏠 گزینه موردنظر را انتخاب کنید.", reply_markup=main_keyboard(message.from_user.id))


def run():
    setup_bot_commands()
    print("Bot is running...")
    logging.info("Bot started")
    while True:
        try:
            bot.infinity_polling(timeout=30, long_polling_timeout=30)
        except KeyboardInterrupt:
            raise
        except Exception as e:
            logging.error("polling crashed: %s", e)
            time.sleep(5)


if __name__ == "__main__":
    run()
