import os
import sqlite3
import time
import logging
import random
import asyncio
import json
import urllib.request
import urllib.error
from typing import Dict, Any, Optional, List

from bale import Bot, CallbackQuery, Message, MenuKeyboardMarkup, MenuKeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton, InputFile, LabeledPrice, SuccessfulPayment, Update

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def load_env_file(path: str = '.env') -> None:
    """
    فایل .env کنار Shop.py را هم می‌خواند تا اگر systemd فقط EnvironmentFile داشت یا دستی اجرا شد،
    BALE_BOT_TOKEN و BALE_ADMIN_ID درست لود شوند. متغیرهای واقعی سیستم اولویت دارند.
    """
    env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), path)
    if not os.path.exists(env_path):
        return
    try:
        with open(env_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("'")
                os.environ.setdefault(key, value)
    except Exception as e:
        logger.error(f"خطا در خواندن فایل .env: {e}")


load_env_file()

# نکته امنیتی:
# توکن را داخل سورس نگذارید. در سرور این متغیر را ست کنید:
# export BALE_BOT_TOKEN="YOUR_BOT_TOKEN"
TOKEN = os.getenv("BALE_BOT_TOKEN", "PUT_YOUR_BALE_BOT_TOKEN_HERE")
ADMIN_ID = int(os.getenv("BALE_ADMIN_ID", "2024562679"))
CHANNEL_USERNAME = os.getenv("BALE_CHANNEL_USERNAME", "@testkonino")
DB_PATH = os.getenv("BALE_BOT_DB", "bot.db")

logger.info(f"Bot config loaded: ADMIN_ID={ADMIN_ID}, DB_PATH={DB_PATH}")

if TOKEN == "PUT_YOUR_BALE_BOT_TOKEN_HERE":
    logger.warning("BALE_BOT_TOKEN تنظیم نشده است. قبل از اجرای واقعی، توکن را در متغیر محیطی قرار دهید.")


bot = Bot(token=TOKEN)

# ------------------------- رفع خطای «ربات پاسخگو نیست» در پرداخت بله -------------------------
# در برخی نسخه‌های API بله، هنگام پرداخت کیف پول/فاکتور، یک pre_checkout_query به ربات ارسال می‌شود
# و اگر ظرف چند ثانیه تایید نشود، کاربر خطای «ربات پاسخگو نیست» می‌بیند.
# python-bale-bot در نسخه 2.5.0 این آپدیت را به‌صورت رویداد جداگانه در اختیار سورس نمی‌گذارد،
# بنابراین قبل از تبدیل Update، آن را مستقیم جواب می‌دهیم.

def answer_pre_checkout_query_sync(query_id: str, ok: bool = True, error_message: str = "") -> bool:
    if not query_id:
        return False
    url = f"https://tapi.bale.ai/bot{TOKEN}/answerPreCheckoutQuery"
    payload = {
        "pre_checkout_query_id": str(query_id),
        "ok": bool(ok),
    }
    if error_message:
        payload["error_message"] = error_message
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=4) as resp:
            body = resp.read().decode("utf-8", errors="ignore")
            logger.info(f"pre_checkout_query answered: id={query_id}, response={body[:300]}")
            return True
    except Exception as e:
        logger.error(f"خطا در پاسخ به pre_checkout_query بله: id={query_id}, error={e}")
        return False


def patch_bale_update_for_pre_checkout() -> None:
    original_from_dict = Update.from_dict

    @classmethod
    def patched_from_dict(cls, data=None, *args, **kwargs):
        """
        نسخه‌های مختلف python-bale-bot تابع Update.from_dict را گاهی با
        from_dict(data=..., bot=...) و گاهی به شکل from_dict(data, bot) صدا می‌زنند.
        این پچ باید هر دو حالت را بپذیرد تا کل دریافت آپدیت‌های ربات کرش نکند.
        """
        try:
            if data is None:
                data = kwargs.get("data")
            raw = data or {}
            pre_checkout = raw.get("pre_checkout_query") or raw.get("preCheckoutQuery")
            if isinstance(pre_checkout, dict):
                query_id = pre_checkout.get("id") or pre_checkout.get("pre_checkout_query_id")
                invoice_payload = pre_checkout.get("invoice_payload") or pre_checkout.get("payload") or ""
                total_amount = pre_checkout.get("total_amount")
                logger.info(
                    f"pre_checkout_query received: id={query_id}, payload={invoice_payload}, total_amount={total_amount}"
                )
                answer_pre_checkout_query_sync(str(query_id), ok=True)
        except Exception as e:
            logger.error(f"خطا در patch pre_checkout_query: {e}")

        try:
            return original_from_dict(data=data, *args, **kwargs)
        except TypeError:
            bot_obj = kwargs.get("bot") or kwargs.get("bot_obj") or (args[0] if args else None)
            if bot_obj is not None:
                return original_from_dict(data, bot_obj)
            return original_from_dict(data)

    Update.from_dict = patched_from_dict
    logger.info("Bale pre_checkout_query patch enabled")


patch_bale_update_for_pre_checkout()

conn = sqlite3.connect(DB_PATH, check_same_thread=False)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()

user_data: Dict[int, Dict[str, Any]] = {}

# ------------------------- دیتابیس و تنظیمات -------------------------

def ensure_column(table: str, column: str, definition: str) -> None:
    cursor.execute(f"PRAGMA table_info({table})")
    existing = [col[1] for col in cursor.fetchall()]
    if column not in existing:
        cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}")


def init_db() -> None:
    cursor.execute("""CREATE TABLE IF NOT EXISTS users (
        user_id INTEGER PRIMARY KEY,
        username TEXT,
        full_name TEXT,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )""")
    ensure_column("users", "username", "TEXT")
    ensure_column("users", "full_name", "TEXT")
    ensure_column("users", "created_at", "TIMESTAMP")
    cursor.execute("UPDATE users SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL")

    cursor.execute("""CREATE TABLE IF NOT EXISTS ads (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        ad_code TEXT UNIQUE,
        user_id INTEGER,
        template_type TEXT,
        photos TEXT,
        type TEXT,
        status TEXT,
        size TEXT,
        city TEXT,
        price TEXT,
        phone TEXT,
        bale TEXT,
        description TEXT,
        amount INTEGER DEFAULT 0,
        payment_method TEXT,
        payment_status TEXT DEFAULT 'unpaid',
        payment_id INTEGER,
        published_message_ids TEXT,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        admin_status TEXT DEFAULT 'pending'
    )""")
    ad_columns = {
        "ad_code": "TEXT UNIQUE",
        "user_id": "INTEGER",
        "template_type": "TEXT",
        "photos": "TEXT",
        "type": "TEXT",
        "status": "TEXT",
        "size": "TEXT",
        "city": "TEXT",
        "price": "TEXT",
        "phone": "TEXT",
        "bale": "TEXT",
        "description": "TEXT",
        "amount": "INTEGER DEFAULT 0",
        "payment_method": "TEXT",
        "payment_status": "TEXT DEFAULT 'unpaid'",
        "payment_id": "INTEGER",
        "published_message_ids": "TEXT",
        "admin_status": "TEXT DEFAULT 'pending'",
        "created_at": "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
    }
    for col, definition in ad_columns.items():
        ensure_column("ads", col, definition)

    cursor.execute("""CREATE TABLE IF NOT EXISTS settings (
        key TEXT PRIMARY KEY,
        value TEXT
    )""")

    cursor.execute("""CREATE TABLE IF NOT EXISTS payments (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER,
        ad_code TEXT,
        amount INTEGER,
        method TEXT,
        status TEXT DEFAULT 'pending',
        description TEXT,
        provider_payload TEXT,
        receipt_type TEXT,
        receipt_file_id TEXT,
        receipt_text TEXT,
        admin_note TEXT,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )""")
    payment_columns = {
        "user_id": "INTEGER",
        "ad_code": "TEXT",
        "amount": "INTEGER",
        "method": "TEXT",
        "status": "TEXT DEFAULT 'pending'",
        "description": "TEXT",
        "provider_payload": "TEXT",
        "receipt_type": "TEXT",
        "receipt_file_id": "TEXT",
        "receipt_text": "TEXT",
        "admin_note": "TEXT",
        "created_at": "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
        "updated_at": "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
    }
    for col, definition in payment_columns.items():
        ensure_column("payments", col, definition)

    defaults = {
        "support_text": "درصورت بروز هرگونه مشکل با این آیدی ارتباط برقرار کنید:\n@ADMIN_120",
        "channels_text": "📢 کانال‌های ما:\n@channel1\n@channel2",
        "publish_channel": CHANNEL_USERNAME,
        "payment_required": "1",
        "bale_payment_enabled": "1",
        "bale_provider_token": "",
        "card_payment_enabled": "1",
        "card_number": "",
        "card_owner": "",
        "simple_ad_price": "100000",
        "special_ad_price": "135000",
        "force_join_enabled": "0",
        "force_join_channel": "",
    }
    for key, value in defaults.items():
        cursor.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", (key, value))
    conn.commit()


init_db()


def get_setting(key: str, default: str = "") -> str:
    cursor.execute("SELECT value FROM settings WHERE key=?", (key,))
    row = cursor.fetchone()
    return str(row["value"]) if row and row["value"] is not None else default


def set_setting(key: str, value: str) -> None:
    cursor.execute(
        "INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value",
        (key, value),
    )
    conn.commit()


def setting_bool(key: str, default: bool = False) -> bool:
    value = get_setting(key, "1" if default else "0").strip()
    return value in ["1", "true", "True", "on", "ON", "yes", "بله", "روشن"]


def setting_int(key: str, default: int) -> int:
    try:
        return int(get_setting(key, str(default)).replace(",", "").strip())
    except Exception:
        return default


def money(amount: int) -> str:
    return f"{amount:,}".replace(",", "٬")


def toman_to_rial(amount_toman: int) -> int:
    """قیمت‌های پنل و دیتابیس بر اساس تومان هستند؛ مبلغ فاکتور بله باید ریال باشد."""
    try:
        return int(amount_toman) * 10
    except Exception:
        return 0


def rial_to_toman(amount_rial: int) -> int:
    """مبلغ برگشتی پرداخت بله ریال است؛ برای گزارش‌های ربات به تومان تبدیل می‌شود."""
    try:
        return int(amount_rial) // 10
    except Exception:
        return 0


def normalize_channel_username(raw: str) -> str:
    value = (raw or "").strip()
    if not value:
        return ""
    # اگر ادمین لینک یا نام را با فاصله فرستاد، فقط مقدار اصلی را نگه می‌داریم.
    value = value.replace("https://ble.ir/", "").replace("http://ble.ir/", "")
    value = value.replace("https://bale.ai/", "").replace("http://bale.ai/", "")
    value = value.strip().split()[0]
    if value.startswith("@"):
        return value
    return "@" + value


def get_publish_channel() -> str:
    return normalize_channel_username(get_setting("publish_channel", CHANNEL_USERNAME))


def get_ad_amount(template_type: str) -> int:
    if template_type == "special":
        return setting_int("special_ad_price", 135000)
    return setting_int("simple_ad_price", 100000)


def mask_value(value: str, keep: int = 4) -> str:
    if not value:
        return "تنظیم نشده"
    if len(value) <= keep:
        return "*" * len(value)
    return "*" * max(0, len(value) - keep) + value[-keep:]


# ------------------------- کیبوردها -------------------------

def get_main_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton("📝 ثبت آگهی"), row=1)
    kb.add(MenuKeyboardButton("📞 پشتیبانی"), row=2)
    kb.add(MenuKeyboardButton("📢 کانال‌های ما"), row=2)
    return kb


def get_admin_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton("⚙️ تنظیمات پرداخت"), row=1)
    kb.add(MenuKeyboardButton("👥 کاربران"), row=2)
    kb.add(MenuKeyboardButton("📣 ارسال همگانی"), row=2)
    kb.add(MenuKeyboardButton("📮 تنظیم کانال ارسال"), row=3)
    kb.add(MenuKeyboardButton("🔐 قفل اجباری کانال"), row=3)
    kb.add(MenuKeyboardButton("📢 تنظیم متن کانال‌ها"), row=4)
    kb.add(MenuKeyboardButton("☎️ تنظیم پشتیبانی"), row=4)
    kb.add(MenuKeyboardButton("💰 واریزی‌ها"), row=5)
    kb.add(MenuKeyboardButton("🏠 منوی اصلی"), row=6)
    return kb


def get_payment_settings_keyboard():
    kb = MenuKeyboardMarkup()
    bale_label = "🔴 خاموش کردن پرداخت بله" if setting_bool("bale_payment_enabled", True) else "🟢 روشن کردن پرداخت بله"
    card_label = "🔴 خاموش کردن کارت به کارت" if setting_bool("card_payment_enabled", True) else "🟢 روشن کردن کارت به کارت"
    required_label = "🔓 غیرفعال کردن الزام پرداخت" if setting_bool("payment_required", True) else "🔒 فعال کردن الزام پرداخت"
    kb.add(MenuKeyboardButton(bale_label), row=1)
    kb.add(MenuKeyboardButton("🔑 تنظیم توکن پرداخت بله"), row=2)
    kb.add(MenuKeyboardButton(card_label), row=3)
    kb.add(MenuKeyboardButton("💳 تنظیم شماره کارت"), row=4)
    kb.add(MenuKeyboardButton("👤 تنظیم نام صاحب کارت"), row=4)
    kb.add(MenuKeyboardButton("💵 قیمت آگهی ساده"), row=5)
    kb.add(MenuKeyboardButton("💎 قیمت آگهی ویژه"), row=5)
    kb.add(MenuKeyboardButton(required_label), row=6)
    kb.add(MenuKeyboardButton("🔙 برگشت به پنل"), row=7)
    return kb


def get_force_join_settings_keyboard():
    kb = MenuKeyboardMarkup()
    toggle_label = "🔓 خاموش کردن قفل اجباری" if setting_bool("force_join_enabled", False) else "🔒 روشن کردن قفل اجباری"
    kb.add(MenuKeyboardButton(toggle_label), row=1)
    kb.add(MenuKeyboardButton("📢 تنظیم کانال قفل اجباری"), row=2)
    kb.add(MenuKeyboardButton("🔙 برگشت به پنل"), row=3)
    return kb


def get_force_join_user_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton("✅ بررسی عضویت"), row=1)
    return kb


def get_force_join_settings_text() -> str:
    configured_force_channel = normalize_channel_username(get_setting("force_join_channel", ""))
    publish_channel = get_publish_channel() or "تنظیم نشده"
    active_channel = get_force_join_channel() or "تنظیم نشده"
    source_text = "کانال جداگانه قفل اجباری" if configured_force_channel else "همان کانال سفارشات/ارسال آگهی"
    return f"""🔐 تنظیمات قفل اجباری کانال

وضعیت: {'روشن ✅' if setting_bool('force_join_enabled', False) else 'خاموش ❌'}
📮 کانال سفارشات/ارسال آگهی: {publish_channel}
📢 کانال فعال برای قفل عضویت: {active_channel}
منبع قفل: {source_text}

اگر روشن باشد، کاربر قبل از استفاده از دکمه‌های ربات باید عضو کانال شود و دکمه «✅ بررسی عضویت» را بزند.
بعداً هم اگر کاربر از کانال خارج شود، ربات در پیام بعدی دوباره منو را قفل می‌کند.
ربات باید داخل همین کانال ادمین باشد تا بتواند عضویت را بررسی کند."""


def get_template_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton(f"آگهی ساده ({money(get_ad_amount('simple'))} تومان)"), row=1)
    kb.add(MenuKeyboardButton(f"آگهی ویژه ({money(get_ad_amount('special'))} تومان)"), row=2)
    kb.add(MenuKeyboardButton("🔙 برگشت به منو"), row=3)
    return kb


def get_photo_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton("✅ رفتن به مرحله بعد"), row=1)
    kb.add(MenuKeyboardButton("🔙 برگشت"), row=2)
    kb.add(MenuKeyboardButton("❌ انصراف"), row=2)
    return kb


def get_back_keyboard(include_skip=True):
    kb = MenuKeyboardMarkup()
    if include_skip:
        kb.add(MenuKeyboardButton("🔙 برگشت"), row=1)
        kb.add(MenuKeyboardButton("⏭ رد کردن"), row=1)
    else:
        kb.add(MenuKeyboardButton("🔙 برگشت"), row=1)
    return kb


def get_phone_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton("🔙 برگشت"), row=1)
    kb.add(MenuKeyboardButton("⏭ رد کردن"), row=1)
    return kb


def get_status_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton("نو"), row=1)
    kb.add(MenuKeyboardButton("در حد نو"), row=2)
    kb.add(MenuKeyboardButton("کارکرده"), row=3)
    kb.add(MenuKeyboardButton("🔙 برگشت"), row=4)
    return kb


def get_edit_keyboard():
    kb = MenuKeyboardMarkup()
    kb.add(MenuKeyboardButton("✅ ثبت نهایی"), row=1)
    kb.add(MenuKeyboardButton("❌ انصراف"), row=1)
    kb.add(MenuKeyboardButton("📸 ویرایش عکس"), row=2)
    kb.add(MenuKeyboardButton("📦 ویرایش نام کالا"), row=2)
    kb.add(MenuKeyboardButton("🛍 ویرایش وضعیت"), row=3)
    kb.add(MenuKeyboardButton("📏 ویرایش سایز"), row=3)
    kb.add(MenuKeyboardButton("📍 ویرایش شهر"), row=4)
    kb.add(MenuKeyboardButton("💰 ویرایش قیمت"), row=4)
    kb.add(MenuKeyboardButton("📞 ویرایش تلفن"), row=5)
    kb.add(MenuKeyboardButton("🆔 ویرایش بله"), row=5)
    kb.add(MenuKeyboardButton("📝 ویرایش توضیحات"), row=6)
    return kb


def get_payment_method_keyboard(template_type: str):
    kb = MenuKeyboardMarkup()
    row = 1
    provider_token = get_setting("bale_provider_token", "").strip()
    if setting_bool("bale_payment_enabled", True) and provider_token:
        kb.add(MenuKeyboardButton("💳 پرداخت آنلاین بله"), row=row)
        row += 1
    if setting_bool("card_payment_enabled", True) and get_setting("card_number", "").strip():
        kb.add(MenuKeyboardButton("🧾 کارت به کارت"), row=row)
        row += 1
    kb.add(MenuKeyboardButton("🔙 برگشت به انتخاب نوع"), row=row)
    kb.add(MenuKeyboardButton("❌ انصراف"), row=row)
    return kb


def get_receipt_review_inline_keyboard(ad_code: str):
    kb = InlineKeyboardMarkup()
    kb.add(InlineKeyboardButton("✅ تأیید پرداخت", callback_data=f"pay_ok:{ad_code}"), row=1)
    kb.add(InlineKeyboardButton("❌ رد پرداخت", callback_data=f"pay_bad:{ad_code}"), row=1)
    return kb


def get_ad_review_inline_keyboard(ad_code: str):
    kb = InlineKeyboardMarkup()
    kb.add(InlineKeyboardButton("✅ تأیید و انتشار", callback_data=f"ad_ok:{ad_code}"), row=1)
    kb.add(InlineKeyboardButton("❌ رد آگهی", callback_data=f"ad_bad:{ad_code}"), row=1)
    return kb


# ------------------------- قالب متن‌ها -------------------------

def generate_ad_code():
    while True:
        code = f"A{random.randint(1000, 9999)}"
        cursor.execute("SELECT id FROM ads WHERE ad_code = ?", (code,))
        if not cursor.fetchone():
            return code


def get_simple_template(data, ad_code):
    return f"""📢 آگهی ساده - کد: {ad_code}

📦 نام کالا: {data.get('type', 'ذکر نشده')}
🛍 وضعیت: {data.get('status', 'ذکر نشده')}
📏 سایز: {data.get('size', 'ذکر نشده')}
📍 شهر: {data.get('city', 'ذکر نشده')}
💰 قیمت: {data.get('price', 'ذکر نشده')} تومان

📝 توضیحات: {data.get('desc', 'ذکر نشده')}

📞 تماس: {data.get('phone', 'ذکر نشده')}
🆔 بله: {data.get('bale', 'ذکر نشده')}

جهت ثبت آگهی:
❌ @admin_agahi_bot
❌ @admin_agahi_bot
❌ @admin_agahi_bot"""


def get_special_template(data, ad_code):
    return f"""✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔴 آگهی ویژه - کد: {ad_code}
🔴 فروش فوری

💵 قیمت : {data.get('price', 'ذکر نشده')} تومان
📦 نام کالا : {data.get('type', 'ذکر نشده')}
🛍 وضعیت : {data.get('status', 'ذکر نشده')}
📏 سایز : {data.get('size', 'ذکر نشده')}
📍 شهر : {data.get('city', 'ذکر نشده')}
🗓 توضیحات : {data.get('desc', 'ذکر نشده')}

📞 شماره تماس : {data.get('phone', 'ذکر نشده')}
🆔 آیدی بله : {data.get('bale', 'ذکر نشده')}

جهت ثبت آگهی:
❌ @admin_agahi_bot
❌ @admin_agahi_bot
❌ @admin_agahi_bot"""


def get_payment_settings_text() -> str:
    return f"""⚙️ تنظیمات پرداخت

🔒 الزام پرداخت قبل از ارسال آگهی به ادمین: {'روشن ✅' if setting_bool('payment_required', True) else 'خاموش ❌'}

💳 پرداخت آنلاین بله: {'روشن ✅' if setting_bool('bale_payment_enabled', True) else 'خاموش ❌'}
🔑 توکن پرداخت بله: {mask_value(get_setting('bale_provider_token', ''), 6)}

🧾 کارت به کارت: {'روشن ✅' if setting_bool('card_payment_enabled', True) else 'خاموش ❌'}
💳 شماره کارت: {get_setting('card_number', 'تنظیم نشده') or 'تنظیم نشده'}
👤 صاحب کارت: {get_setting('card_owner', 'تنظیم نشده') or 'تنظیم نشده'}

💵 قیمت آگهی ساده: {money(get_ad_amount('simple'))} تومان
💎 قیمت آگهی ویژه: {money(get_ad_amount('special'))} تومان

از دکمه‌های زیر برای تنظیم استفاده کنید."""


def get_admin_panel_text() -> str:
    channel = get_publish_channel() or "تنظیم نشده"
    force_channel = get_force_join_channel() or "تنظیم نشده"
    return f"""پنل مدیریت ربات

📮 کانال ارسال آگهی‌ها / سفارشات: {channel}
📢 دکمه «کانال‌های ما»: همان کانال سفارشات را نشان می‌دهد.
🔐 قفل اجباری: {'روشن ✅' if setting_bool('force_join_enabled', False) else 'خاموش ❌'}
📢 کانال فعال قفل اجباری: {force_channel}

برای تغییر کانال مقصد، دکمه «📮 تنظیم کانال ارسال» را بزنید.
برای اجبار عضویت کاربران، دکمه «🔐 قفل اجباری کانال» را بزنید.
ربات باید داخل کانال تنظیم‌شده ادمین باشد و دسترسی لازم داشته باشد."""


# ------------------------- ذخیره و بازیابی آگهی/پرداخت -------------------------

def photos_to_str(photos: List[str]) -> str:
    return ",".join([str(p) for p in photos if p])


def photos_from_str(value: Optional[str]) -> List[str]:
    if not value:
        return []
    return [x for x in value.split(",") if x]


def save_ad_to_db(user_id: int, data: Dict[str, Any], ad_code: str, admin_status: str = "awaiting_payment", payment_status: str = "unpaid", payment_method: Optional[str] = None, payment_id: Optional[int] = None) -> None:
    amount = get_ad_amount(data.get("template_type", "simple"))
    cursor.execute("""
        INSERT INTO ads (
            ad_code, user_id, template_type, photos, type, status, size, city, price, phone, bale,
            description, amount, payment_method, payment_status, payment_id, admin_status
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(ad_code) DO UPDATE SET
            user_id=excluded.user_id,
            template_type=excluded.template_type,
            photos=excluded.photos,
            type=excluded.type,
            status=excluded.status,
            size=excluded.size,
            city=excluded.city,
            price=excluded.price,
            phone=excluded.phone,
            bale=excluded.bale,
            description=excluded.description,
            amount=excluded.amount,
            payment_method=COALESCE(excluded.payment_method, ads.payment_method),
            payment_status=excluded.payment_status,
            payment_id=COALESCE(excluded.payment_id, ads.payment_id),
            admin_status=excluded.admin_status
    """, (
        ad_code, user_id, data.get("template_type", "simple"), photos_to_str(data.get("photos", [])),
        data.get("type", "ذکر نشده"), data.get("status", "ذکر نشده"), data.get("size", "ذکر نشده"),
        data.get("city", "ذکر نشده"), data.get("price", "ذکر نشده"), data.get("phone", "ذکر نشده"),
        data.get("bale", "ذکر نشده"), data.get("desc", "ذکر نشده"), amount, payment_method,
        payment_status, payment_id, admin_status,
    ))
    conn.commit()


def row_to_ad_data(row: sqlite3.Row) -> Dict[str, Any]:
    return {
        "template_type": row["template_type"] or "simple",
        "photos": photos_from_str(row["photos"]),
        "type": row["type"] or "ذکر نشده",
        "status": row["status"] or "ذکر نشده",
        "size": row["size"] or "ذکر نشده",
        "city": row["city"] or "ذکر نشده",
        "price": row["price"] or "ذکر نشده",
        "phone": row["phone"] or "ذکر نشده",
        "bale": row["bale"] or "ذکر نشده",
        "desc": row["description"] or "ذکر نشده",
        "ad_code": row["ad_code"],
    }


def get_ad(ad_code: str) -> Optional[sqlite3.Row]:
    cursor.execute("SELECT * FROM ads WHERE ad_code=?", (ad_code,))
    return cursor.fetchone()


def create_payment(user_id: int, ad_code: str, amount: int, method: str, status: str, description: str = "", provider_payload: str = "") -> int:
    cursor.execute("""
        INSERT INTO payments (user_id, ad_code, amount, method, status, description, provider_payload)
        VALUES (?, ?, ?, ?, ?, ?, ?)
    """, (user_id, ad_code, amount, method, status, description, provider_payload))
    payment_id = cursor.lastrowid
    cursor.execute("UPDATE ads SET payment_id=?, payment_method=?, payment_status=? WHERE ad_code=?", (payment_id, method, status, ad_code))
    conn.commit()
    return payment_id


def update_payment_status(ad_code: str, status: str, admin_note: str = "") -> None:
    cursor.execute("SELECT id FROM payments WHERE ad_code=? ORDER BY id DESC LIMIT 1", (ad_code,))
    row = cursor.fetchone()
    if row:
        cursor.execute(
            "UPDATE payments SET status=?, admin_note=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
            (status, admin_note, row["id"]),
        )
    cursor.execute("UPDATE ads SET payment_status=? WHERE ad_code=?", (status, ad_code))
    conn.commit()


# ------------------------- ارسال‌ها -------------------------

async def safe_send_photo(chat_id, photo_id: str, caption: Optional[str] = None):
    if caption:
        return await bot.send_photo(chat_id, InputFile(photo_id), caption=caption)
    return await bot.send_photo(chat_id, InputFile(photo_id))


def limit_album_caption(text: Optional[str]) -> tuple:
    """
    کپشن در InputMediaPhoto طبق API بله محدودیت ۱۰۲۴ کاراکتر دارد.
    اگر متن آگهی طولانی‌تر بود، کپشن کوتاه می‌شود و متن کامل جداگانه ارسال می‌گردد.
    """
    if not text:
        return None, None
    if len(text) <= 1024:
        return text, None
    return text[:1000].rstrip() + "...", text


def call_bale_api_sync(method_name: str, payload: Dict[str, Any]) -> Dict[str, Any]:
    url = f"https://tapi.bale.ai/bot{TOKEN}/{method_name}"
    body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=body,
        headers={"Content-Type": "application/json; charset=utf-8"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=25) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        try:
            return json.loads(e.read().decode("utf-8"))
        except Exception:
            return {"ok": False, "description": str(e)}
    except Exception as e:
        return {"ok": False, "description": str(e)}


async def call_bale_api(method_name: str, payload: Dict[str, Any]) -> Dict[str, Any]:
    return await asyncio.to_thread(call_bale_api_sync, method_name, payload)


def get_force_join_channel() -> str:
    """
    کانال قفل اجباری اگر جداگانه تنظیم نشده باشد، همان کانال ارسال/سفارشات است.
    این باعث می‌شود دکمه «کانال‌های ما» و قفل عضویت هر دو روی کانال سفارشات کار کنند.
    """
    force_channel = normalize_channel_username(get_setting("force_join_channel", ""))
    if force_channel:
        return force_channel
    return get_publish_channel()


async def is_user_joined_force_channel(user_id: int) -> bool:
    if not setting_bool("force_join_enabled", False):
        return True
    channel = get_force_join_channel()
    if not channel:
        # اگر ادمین قفل را روشن کرده ولی کانال تنظیم نکرده، کاربر را قفل نکنیم.
        logger.warning("قفل اجباری روشن است اما کانال تنظیم نشده است.")
        return True
    result = await call_bale_api("getChatMember", {"chat_id": channel, "user_id": user_id})
    if not result.get("ok"):
        logger.error(f"خطا در بررسی عضویت قفل اجباری: {result}")
        return False
    member = result.get("result") or {}
    status = str(member.get("status", "")).lower()
    return status in ["creator", "administrator", "member", "owner"]


async def require_force_join(message: Message, user_id: int) -> bool:
    if user_id == ADMIN_ID:
        return True
    if not setting_bool("force_join_enabled", False):
        return True
    channel = get_force_join_channel()
    if not channel:
        return True
    if await is_user_joined_force_channel(user_id):
        return True
    join_text = f"🔐 برای استفاده از ربات ابتدا عضو کانال زیر شوید:\n\n{channel}\n\nبعد از عضویت روی «✅ بررسی عضویت» بزنید."
    await message.reply(join_text, components=get_force_join_user_keyboard())
    return False


async def start_ad_form_after_payment(user_id: int, ad_code: str, method: str) -> None:
    row = get_ad(ad_code)
    if not row:
        await bot.send_message(user_id, "پرداخت ثبت شد، اما اطلاعات آگهی پیدا نشد. لطفاً با پشتیبانی تماس بگیرید.")
        return
    user_data[user_id] = {
        "template_type": row["template_type"] or "simple",
        "ad_code": ad_code,
        "step": "photo",
        "photos": [],
        "payment_method": method,
        "payment_paid": True,
    }
    msg = (
        f"✅ پرداخت آگهی {ad_code} تأیید شد.\n\n"
        "حالا ثبت آگهی را شروع کنید. عکس‌های کالا را بفرستید؛ می‌توانید چند عکس بفرستید. "
        "بعد از اتمام روی «✅ رفتن به مرحله بعد» بزنید."
    )
    await bot.send_message(user_id, msg, components=get_photo_keyboard())


def extract_message_ids_from_api_result(result: Dict[str, Any]) -> List[str]:
    ids = []
    data = result.get("result")
    if isinstance(data, list):
        for msg in data:
            if isinstance(msg, dict):
                msg_id = msg.get("message_id") or msg.get("messageId")
                if msg_id is not None:
                    ids.append(str(msg_id))
    elif isinstance(data, dict):
        msg_id = data.get("message_id") or data.get("messageId")
        if msg_id is not None:
            ids.append(str(msg_id))
    return ids


async def send_photo_album(chat_id, photo_ids: List[str], caption: Optional[str] = None) -> List[str]:
    """
    ارسال چند عکس در قالب یک آلبوم با کپشن زیر همان آلبوم.
    اگر API خطا داد، برای جلوگیری از خراب شدن انتشار، به ارسال معمولی fallback می‌کند.
    """
    clean_photos = [p for p in photo_ids if p]
    if not clean_photos:
        if caption:
            sent = await bot.send_message(chat_id, caption)
            sent_id = getattr(sent, "message_id", None)
            return [str(sent_id)] if sent_id else []
        return []

    if len(clean_photos) == 1:
        sent = await safe_send_photo(chat_id, clean_photos[0], caption=caption)
        sent_id = getattr(sent, "message_id", None)
        return [str(sent_id)] if sent_id else []

    album_caption, full_text_after = limit_album_caption(caption)
    media = []
    for i, photo_id in enumerate(clean_photos[:10]):
        item = {"type": "photo", "media": photo_id}
        if i == 0 and album_caption:
            item["caption"] = album_caption
        media.append(item)

    result = await call_bale_api("sendMediaGroup", {"chat_id": chat_id, "media": media})
    if result.get("ok"):
        sent_ids = extract_message_ids_from_api_result(result)
        if full_text_after:
            await bot.send_message(chat_id, "متن کامل آگهی:\n\n" + full_text_after)
        return sent_ids

    logger.error(f"خطا در sendMediaGroup، ارسال معمولی انجام می‌شود: {result}")
    sent_ids = []
    for i, photo_id in enumerate(clean_photos):
        sent = await safe_send_photo(chat_id, photo_id, caption=caption if i == 0 else None)
        sent_id = getattr(sent, "message_id", None)
        if sent_id:
            sent_ids.append(str(sent_id))
    return sent_ids


async def send_to_admin_for_review(user_id: int, data: Dict[str, Any], ad_code: str) -> bool:
    ad_text = get_simple_template(data, ad_code) if data.get("template_type") == "simple" else get_special_template(data, ad_code)
    photos = data.get("photos", [])
    amount = get_ad_amount(data.get("template_type", "simple"))
    try:
        payment_info = f"""\n\n💰 وضعیت پرداخت: پرداخت شده ✅
💵 مبلغ پرداختی: {money(amount)} تومان
🧾 روش پرداخت: {data.get('payment_method', 'ثبت شده')}"""
        await send_photo_album(ADMIN_ID, photos, caption=ad_text + payment_info)
        review_msg = f"""📌 بررسی نهایی آگهی

کد آگهی: {ad_code}

با دکمه‌های زیر آگهی را تأیید و منتشر یا رد کنید."""
        await bot.send_message(ADMIN_ID, review_msg, components=get_ad_review_inline_keyboard(ad_code))
        save_ad_to_db(user_id, data, ad_code, admin_status="pending", payment_status="paid", payment_method=data.get("payment_method"))
        return True
    except Exception as e:
        logger.error(f"خطا در ارسال آگهی به ادمین: {e}")
        return False


async def send_to_admin_from_db(ad_code: str) -> bool:
    row = get_ad(ad_code)
    if not row:
        return False
    data = row_to_ad_data(row)
    data["payment_method"] = row["payment_method"] or "ثبت شده"
    return await send_to_admin_for_review(int(row["user_id"]), data, ad_code)


async def publish_ad_to_channel(ad_code: str) -> bool:
    row = get_ad(ad_code)
    if not row:
        return False
    data = row_to_ad_data(row)
    ad_text = get_simple_template(data, ad_code) if data.get("template_type") == "simple" else get_special_template(data, ad_code)
    photos = data.get("photos", [])
    try:
        publish_channel = get_publish_channel()
        if not publish_channel:
            logger.error("کانال انتشار تنظیم نشده است.")
            return False
        sent_ids = await send_photo_album(publish_channel, photos, caption=ad_text)
        cursor.execute(
            "UPDATE ads SET published_message_ids=?, admin_status='approved' WHERE ad_code=?",
            (",".join(sent_ids), ad_code),
        )
        conn.commit()
        return True
    except Exception as e:
        logger.error(f"خطا در انتشار آگهی در کانال: {e}")
        return False


async def broadcast_to_all_users(original_message, caption_text=""):
    cursor.execute("SELECT user_id FROM users")
    users = cursor.fetchall()
    success_count = 0
    fail_count = 0
    for user in users:
        try:
            uid = user["user_id"]
            if getattr(original_message, "text", None):
                await bot.send_message(uid, original_message.text)
            elif getattr(original_message, "photos", None):
                await bot.send_photo(uid, InputFile(original_message.photos[-1].file_id), caption=caption_text)
            elif getattr(original_message, "photo", None):
                photo = original_message.photo[-1] if isinstance(original_message.photo, list) else original_message.photo
                await bot.send_photo(uid, InputFile(photo.file_id), caption=caption_text)
            elif getattr(original_message, "video", None):
                await bot.send_video(uid, InputFile(original_message.video.file_id), caption=caption_text)
            elif getattr(original_message, "document", None):
                await bot.send_document(uid, InputFile(original_message.document.file_id), caption=caption_text)
            else:
                await bot.send_message(uid, str(caption_text))
            success_count += 1
            await asyncio.sleep(0.05)
        except Exception:
            fail_count += 1
    return success_count, fail_count


# ------------------------- پرداخت -------------------------

async def send_bale_invoice(message: Message, user_id: int, data: Dict[str, Any], ad_code: str) -> bool:
    provider_token = get_setting("bale_provider_token", "").strip()
    if not provider_token:
        await message.reply("❌ توکن پرداخت بله تنظیم نشده است. لطفاً با پشتیبانی تماس بگیرید.", components=get_payment_method_keyboard(data.get("template_type", "simple")))
        return False

    amount_toman = get_ad_amount(data.get("template_type", "simple"))
    amount_rial = toman_to_rial(amount_toman)
    payload = f"ad:{ad_code}:user:{user_id}"
    create_payment(user_id, ad_code, amount_toman, "bale", "invoice_sent", f"پرداخت آنلاین بله برای آگهی {ad_code}", payload)
    save_ad_to_db(user_id, data, ad_code, admin_status="awaiting_payment", payment_status="invoice_sent", payment_method="bale")

    title = f"پرداخت ثبت آگهی {ad_code}"
    description = f"هزینه ثبت {'آگهی ویژه' if data.get('template_type') == 'special' else 'آگهی ساده'}"
    try:
        await message.chat.send_invoice(
            title=title,
            description=description,
            provider_token=provider_token,
            payload=payload,
            # بله مبلغ invoice را بر اساس ریال می‌خواهد؛ قیمت‌های پنل ادمین بر اساس تومان ذخیره می‌شوند.
            prices=[LabeledPrice(label=description, amount=amount_rial)],
        )
        # کاربر را در انتخاب روش پرداخت نگه می‌داریم تا اگر پرداخت بله انجام نشد، بتواند کارت‌به‌کارت را انتخاب کند.
        data["step"] = "payment_method"
        await message.reply(
            f"✅ فاکتور پرداخت بله ارسال شد.\n\nمبلغ قابل نمایش: {money(amount_toman)} تومان\nمبلغ ارسالی به بله: {money(amount_rial)} ریال\n\nبعد از پرداخت موفق، فرم ثبت آگهی برای شما باز می‌شود.\n\nاگر پرداخت بله انجام نشد یا منصرف شدید، می‌توانید از همینجا روش کارت‌به‌کارت را انتخاب کنید.",
            components=get_payment_method_keyboard(data.get("template_type", "simple")),
        )
        return True
    except Exception as e:
        logger.error(f"خطا در ارسال فاکتور پرداخت بله: {e}")
        await message.reply("❌ خطا در ارسال فاکتور پرداخت بله. می‌توانید کارت‌به‌کارت را انتخاب کنید یا با پشتیبانی تماس بگیرید.", components=get_payment_method_keyboard(data.get("template_type", "simple")))
        return False


def get_receipt_file(message: Message):
    if getattr(message, "photos", None):
        return "photo", message.photos[-1].file_id
    if getattr(message, "photo", None):
        photo = message.photo[-1] if isinstance(message.photo, list) else message.photo
        return "photo", photo.file_id
    if getattr(message, "document", None):
        return "document", message.document.file_id
    return "text", None


def get_user_display_from_message(message: Message, user_id: int) -> Dict[str, str]:
    author = getattr(message, "author", None)
    username = getattr(author, "username", "") or ""
    first_name = getattr(author, "first_name", "") or ""
    last_name = getattr(author, "last_name", "") or ""
    full_name = (first_name + " " + last_name).strip()

    cursor.execute("SELECT username, full_name FROM users WHERE user_id=?", (user_id,))
    row = cursor.fetchone()
    if row:
        username = username or (row["username"] or "")
        full_name = full_name or (row["full_name"] or "")

    return {
        "username": username or "بدون نام کاربری",
        "full_name": full_name or "بدون نام",
    }


def template_type_fa(template_type: str) -> str:
    return "آگهی ویژه" if template_type == "special" else "آگهی ساده"


def receipt_type_fa(receipt_type: str) -> str:
    return {
        "photo": "عکس رسید",
        "document": "فایل رسید",
        "text": "متن رسید",
    }.get(receipt_type, receipt_type or "نامشخص")


async def send_card_receipt_to_admin(
    user_id: int,
    ad_code: str,
    amount: int,
    template_type: str,
    receipt_type: str,
    file_id: Optional[str],
    receipt_text: str,
    payment_id: Optional[int],
    user_display: Dict[str, str],
) -> bool:
    """
    رسید کارت‌به‌کارت را با حداکثر اطمینان برای ادمین می‌فرستد.
    حالت اصلی: دکمه‌های تأیید/رد زیر خود رسید می‌آیند.
    حالت پشتیبان: اگر ارسال رسانه با دکمه خطا داد، رسید/اطلاعات به‌صورت پیام جدا با همان دکمه‌ها ارسال می‌شود.
    """
    review_keyboard = get_receipt_review_inline_keyboard(ad_code)
    username = user_display.get("username", "بدون نام کاربری")
    full_name = user_display.get("full_name", "بدون نام")
    card_number = get_setting("card_number", "").strip() or "تنظیم نشده"
    card_owner = get_setting("card_owner", "").strip() or "تنظیم نشده"

    admin_caption = f"""🧾 رسید کارت به کارت جدید

کد آگهی: {ad_code}
نوع آگهی: {template_type_fa(template_type)}
شناسه پرداخت: {payment_id or 'ثبت نشده'}

👤 کاربر: {full_name}
🆔 آیدی عددی: {user_id}
🔗 یوزرنیم: {username}

💵 مبلغ: {money(amount)} تومان
💳 کارت مقصد: {card_number}
👤 صاحب کارت: {card_owner}
📎 نوع رسید: {receipt_type_fa(receipt_type)}

بعد از تأیید پرداخت، فرم ثبت آگهی برای کاربر باز می‌شود.
با دکمه‌های زیر پرداخت را تأیید یا رد کنید."""

    text_receipt_block = ""
    if receipt_text and receipt_text.strip():
        text_receipt_block = f"\n\nمتن/توضیح رسید:\n{receipt_text.strip()}"

    try:
        if receipt_type == "photo" and file_id:
            try:
                await bot.send_photo(ADMIN_ID, InputFile(file_id), caption=admin_caption + text_receipt_block, components=review_keyboard)
                return True
            except Exception as e:
                logger.error(f"خطا در ارسال عکس رسید با دکمه، تلاش پشتیبان: {e}")
                try:
                    await bot.send_photo(ADMIN_ID, InputFile(file_id), caption="📎 تصویر رسید کارت به کارت")
                except Exception as media_error:
                    logger.error(f"خطا در ارسال عکس رسید حتی بدون دکمه: {media_error}")
                await bot.send_message(ADMIN_ID, admin_caption + text_receipt_block + "\n\n⚠️ دکمه‌ها به‌صورت پیام پشتیبان ارسال شدند.", components=review_keyboard)
                return True

        if receipt_type == "document" and file_id:
            try:
                await bot.send_document(ADMIN_ID, InputFile(file_id), caption=admin_caption + text_receipt_block, components=review_keyboard)
                return True
            except Exception as e:
                logger.error(f"خطا در ارسال فایل رسید با دکمه، تلاش پشتیبان: {e}")
                try:
                    await bot.send_document(ADMIN_ID, InputFile(file_id), caption="📎 فایل رسید کارت به کارت")
                except Exception as media_error:
                    logger.error(f"خطا در ارسال فایل رسید حتی بدون دکمه: {media_error}")
                await bot.send_message(ADMIN_ID, admin_caption + text_receipt_block + "\n\n⚠️ دکمه‌ها به‌صورت پیام پشتیبان ارسال شدند.", components=review_keyboard)
                return True

        await bot.send_message(ADMIN_ID, admin_caption + text_receipt_block, components=review_keyboard)
        return True
    except Exception as e:
        logger.error(f"ارسال رسید کارت‌به‌کارت به ادمین کاملاً ناموفق بود: {e}")
        return False


async def start_card_payment(message: Message, user_id: int, data: Dict[str, Any], ad_code: str) -> None:
    amount = get_ad_amount(data.get("template_type", "simple"))
    card_number = get_setting("card_number", "").strip()
    card_owner = get_setting("card_owner", "").strip()
    if not card_number:
        await message.reply("❌ شماره کارت تنظیم نشده است. لطفاً با پشتیبانی تماس بگیرید.", components=get_payment_method_keyboard(data.get("template_type", "simple")))
        return

    create_payment(user_id, ad_code, amount, "card", "waiting_receipt", f"کارت به کارت برای آگهی {ad_code}")
    save_ad_to_db(user_id, data, ad_code, admin_status="awaiting_payment", payment_status="waiting_receipt", payment_method="card")
    data["step"] = "card_receipt"
    card_text = f"""🧾 پرداخت کارت به کارت

مبلغ قابل پرداخت: {money(amount)} تومان
شماره کارت:
<code>{card_number}</code>
صاحب کارت: {card_owner or 'تنظیم نشده'}

بعد از واریز، تصویر رسید یا متن اطلاعات پرداخت را همینجا ارسال کنید.
بعد از تأیید رسید توسط ادمین، فرم ثبت آگهی برای شما باز می‌شود."""
    await message.reply(card_text, components=get_back_keyboard(include_skip=False))


async def submit_card_receipt(message: Message, user_id: int, data: Dict[str, Any]) -> None:
    ad_code = data.get("ad_code")
    if not ad_code:
        await message.reply("❌ کد آگهی پیدا نشد. لطفاً دوباره ثبت آگهی را شروع کنید.", components=get_main_keyboard())
        user_data.pop(user_id, None)
        return

    receipt_type, file_id = get_receipt_file(message)
    receipt_text = getattr(message, "text", None) or getattr(message, "caption", None) or ""
    if receipt_type == "text" and not receipt_text.strip():
        await message.reply("لطفاً تصویر رسید، فایل رسید یا متن اطلاعات پرداخت را ارسال کنید.")
        return

    amount = get_ad_amount(data.get("template_type", "simple"))
    cursor.execute("SELECT id FROM payments WHERE ad_code=? ORDER BY id DESC LIMIT 1", (ad_code,))
    pay = cursor.fetchone()
    payment_id = None

    if pay:
        payment_id = pay["id"]
        cursor.execute("""
            UPDATE payments SET status='receipt_pending', receipt_type=?, receipt_file_id=?, receipt_text=?, updated_at=CURRENT_TIMESTAMP
            WHERE id=?
        """, (receipt_type, file_id, receipt_text, payment_id))
    else:
        # حالت پشتیبان: اگر به هر دلیل رکورد پرداخت قبلاً ساخته نشده بود، اینجا ساخته می‌شود تا ادمین چیزی را از دست ندهد.
        payment_id = create_payment(user_id, ad_code, amount, "card", "receipt_pending", f"رسید کارت به کارت برای آگهی {ad_code}")
        cursor.execute("""
            UPDATE payments SET receipt_type=?, receipt_file_id=?, receipt_text=?, updated_at=CURRENT_TIMESTAMP
            WHERE id=?
        """, (receipt_type, file_id, receipt_text, payment_id))

    cursor.execute("""
        UPDATE ads
        SET payment_status='receipt_pending', admin_status='payment_review', payment_method='card', payment_id=?
        WHERE ad_code=?
    """, (payment_id, ad_code))
    conn.commit()

    user_display = get_user_display_from_message(message, user_id)
    ok = await send_card_receipt_to_admin(
        user_id=user_id,
        ad_code=ad_code,
        amount=amount,
        template_type=data.get("template_type", "simple"),
        receipt_type=receipt_type,
        file_id=file_id,
        receipt_text=receipt_text,
        payment_id=payment_id,
        user_display=user_display,
    )

    if ok:
        await message.reply("✅ رسید شما برای ادمین ارسال شد. بعد از تأیید پرداخت، فرم ثبت آگهی برای شما باز می‌شود.", components=get_main_keyboard())
        user_data.pop(user_id, None)
    else:
        # رسید در دیتابیس ذخیره شده، ولی ادمین پیام نگرفته؛ کاربر را در همین مرحله نگه می‌داریم تا دوباره ارسال کند.
        data["step"] = "card_receipt"
        await message.reply("❌ رسید ذخیره شد، اما ارسال آن به ادمین با خطا مواجه شد. لطفاً چند لحظه بعد دوباره همان رسید را ارسال کنید یا با پشتیبانی تماس بگیرید.", components=get_back_keyboard(include_skip=False))


# ------------------------- مراحل -------------------------

def go_back_one_step(user_id):
    data = user_data.get(user_id)
    if not data:
        return False
    step = data.get("step")
    steps = ["template", "photo", "name", "status", "size", "city", "price", "phone", "bale", "desc", "final", "payment_method"]
    if step in steps:
        idx = steps.index(step)
        if idx > 0:
            data["step"] = steps[idx - 1]
            return steps[idx - 1]
    return None


async def show_final_preview(user_id, message):
    data = user_data.get(user_id)
    if not data:
        await message.reply("داده‌ای یافت نشد.", components=get_main_keyboard())
        return
    ad_code = data.get("ad_code", "????")
    preview_text = get_simple_template(data, ad_code) if data["template_type"] == "simple" else get_special_template(data, ad_code)
    photos = data.get("photos", [])
    if photos:
        for i, photo_id in enumerate(photos):
            if i == 0:
                await bot.send_photo(user_id, InputFile(photo_id), caption=preview_text)
            else:
                await bot.send_photo(user_id, InputFile(photo_id))
    else:
        await message.reply(preview_text)
    amount = get_ad_amount(data.get("template_type", "simple"))
    await message.reply(f"مبلغ ثبت این آگهی: {money(amount)} تومان\n\nبرای ویرایش از دکمه‌ها استفاده کنید. در صورت صحت «✅ ثبت نهایی» را بزنید.", components=get_edit_keyboard())


async def show_payment_method(user_id: int, message: Message) -> None:
    data = user_data.get(user_id)
    if not data:
        await message.reply("داده‌ای یافت نشد.", components=get_main_keyboard())
        return
    ad_code = data.get("ad_code") or generate_ad_code()
    data["ad_code"] = ad_code
    save_ad_to_db(user_id, data, ad_code, admin_status="awaiting_payment", payment_status="unpaid")
    amount = get_ad_amount(data.get("template_type", "simple"))
    provider_ready = setting_bool("bale_payment_enabled", True) and bool(get_setting("bale_provider_token", "").strip())
    card_ready = setting_bool("card_payment_enabled", True) and bool(get_setting("card_number", "").strip())
    if not provider_ready and not card_ready:
        await message.reply("❌ هیچ روش پرداختی فعال/تنظیم نشده است. لطفاً با پشتیبانی تماس بگیرید.", components=get_main_keyboard())
        return
    data["step"] = "payment_method"
    await message.reply(f"روش پرداخت را انتخاب کنید:\n\nکد آگهی: {ad_code}\nمبلغ: {money(amount)} تومان", components=get_payment_method_keyboard(data.get("template_type", "simple")))


# ------------------------- رویداد پرداخت موفق بله -------------------------

@bot.event
async def on_successful_payment(successful_payment: SuccessfulPayment):
    try:
        payload = getattr(successful_payment, "payload", None) or getattr(successful_payment, "invoice_payload", None) or ""
        # مبلغ برگشتی بله ریال است. برای دیتابیس/گزارش‌ها به تومان تبدیل می‌کنیم.
        total_amount_rial = int(getattr(successful_payment, "total_amount", 0) or 0)
        total_amount_toman = rial_to_toman(total_amount_rial) if total_amount_rial else 0
        # payload format: ad:A1234:user:123456
        parts = payload.split(":")
        ad_code = parts[1] if len(parts) >= 2 and parts[0] == "ad" else ""
        user_id = int(parts[3]) if len(parts) >= 4 and parts[2] == "user" else 0
        if not ad_code:
            logger.error(f"پرداخت موفق با payload نامعتبر دریافت شد: {payload}")
            return

        cursor.execute("SELECT id FROM payments WHERE provider_payload=? ORDER BY id DESC LIMIT 1", (payload,))
        pay = cursor.fetchone()
        if pay:
            cursor.execute(
                "UPDATE payments SET status='approved', amount=COALESCE(NULLIF(?, 0), amount), updated_at=CURRENT_TIMESTAMP WHERE id=?",
                (total_amount_toman, pay["id"]),
            )
        cursor.execute("UPDATE ads SET payment_status='paid', admin_status='pending', payment_method='bale' WHERE ad_code=?", (ad_code,))
        conn.commit()

        row = get_ad(ad_code)
        if row:
            user_id = int(row["user_id"])
        await start_ad_form_after_payment(user_id, ad_code, "bale")
    except Exception as e:
        logger.error(f"خطا در پردازش پرداخت موفق بله: {e}")


# ------------------------- دکمه‌های ادمین -------------------------

async def answer_callback_safely(callback: CallbackQuery, text: str = "") -> None:
    try:
        if hasattr(callback, "answer"):
            result = callback.answer(text) if text else callback.answer()
            if hasattr(result, "__await__"):
                await result
    except Exception:
        pass


@bot.event
async def on_callback(callback: CallbackQuery):
    try:
        user_id = getattr(getattr(callback, "from_user", None), "id", None) or getattr(getattr(callback, "user", None), "id", None)
        data = getattr(callback, "data", "") or ""
        message = getattr(callback, "message", None)

        if user_id != ADMIN_ID:
            await answer_callback_safely(callback, "دسترسی ندارید")
            if message:
                await message.reply("❌ شما دسترسی ادمین ندارید.")
            return

        if ":" not in data:
            await answer_callback_safely(callback)
            return

        action, ad_code = data.split(":", 1)
        ad_code = ad_code.strip()
        row = get_ad(ad_code)
        if not row:
            await answer_callback_safely(callback, "یافت نشد")
            if message:
                await message.reply(f"❌ آگهی با کد {ad_code} یافت نشد.")
            return

        if action == "pay_ok":
            if row["payment_status"] == "paid":
                await answer_callback_safely(callback, "قبلاً تأیید شده")
                if message:
                    await message.reply(f"ℹ️ پرداخت آگهی {ad_code} قبلاً تأیید شده بود.")
                return
            update_payment_status(ad_code, "paid")
            cursor.execute("UPDATE ads SET admin_status='pending', payment_status='paid', payment_method='card' WHERE ad_code=?", (ad_code,))
            conn.commit()
            await start_ad_form_after_payment(int(row["user_id"]), ad_code, "card")
            await answer_callback_safely(callback, "پرداخت تأیید شد")
            if message:
                await message.reply(f"✅ پرداخت آگهی {ad_code} تأیید شد و فرم ثبت آگهی برای کاربر باز شد.")
            return

        if action == "pay_bad":
            reason = "رسید پرداخت تأیید نشد."
            update_payment_status(ad_code, "rejected", reason)
            cursor.execute("UPDATE ads SET admin_status='payment_rejected', payment_status='rejected' WHERE ad_code=?", (ad_code,))
            conn.commit()
            await bot.send_message(int(row["user_id"]), f"❌ پرداخت کارت به کارت آگهی {ad_code} رد شد.\nعلت: {reason}")
            await answer_callback_safely(callback, "پرداخت رد شد")
            if message:
                await message.reply(f"❌ پرداخت آگهی {ad_code} رد شد.")
            return

        if action == "ad_ok":
            if setting_bool("payment_required", True) and row["payment_status"] != "paid":
                await answer_callback_safely(callback, "پرداخت تأیید نشده")
                if message:
                    await message.reply(f"❌ آگهی {ad_code} هنوز پرداخت تأییدشده ندارد.")
                return
            if row["admin_status"] == "approved":
                await answer_callback_safely(callback, "قبلاً تأیید شده")
                if message:
                    await message.reply(f"ℹ️ آگهی {ad_code} قبلاً تأیید شده بود.")
                return
            ok = await publish_ad_to_channel(ad_code)
            if ok:
                await bot.send_message(int(row["user_id"]), f"✅ آگهی شما با کد {ad_code} تأیید و در کانال منتشر شد.")
                await answer_callback_safely(callback, "منتشر شد")
                if message:
                    await message.reply(f"✅ آگهی {ad_code} تأیید و منتشر شد.")
            else:
                cursor.execute("UPDATE ads SET admin_status='approved' WHERE ad_code=?", (ad_code,))
                conn.commit()
                await bot.send_message(int(row["user_id"]), f"✅ آگهی شما با کد {ad_code} تأیید شد، اما انتشار در کانال با خطا مواجه شد.")
                await answer_callback_safely(callback, "خطا در انتشار")
                if message:
                    await message.reply(f"⚠️ آگهی {ad_code} تأیید شد، اما انتشار در کانال خطا داد. دسترسی ربات به کانال را بررسی کنید.")
            return

        if action == "ad_bad":
            reason = "در صورت نیاز با پشتیبانی تماس بگیرید."
            if row["admin_status"] == "rejected":
                await answer_callback_safely(callback, "قبلاً رد شده")
                if message:
                    await message.reply(f"ℹ️ آگهی {ad_code} قبلاً رد شده بود.")
                return
            cursor.execute("UPDATE ads SET admin_status='rejected' WHERE ad_code=?", (ad_code,))
            conn.commit()
            await bot.send_message(int(row["user_id"]), f"❌ آگهی شما با کد {ad_code} توسط ادمین رد شد.\n{reason}")
            await answer_callback_safely(callback, "آگهی رد شد")
            if message:
                await message.reply(f"❌ آگهی {ad_code} رد شد.")
            return

        await answer_callback_safely(callback)
    except Exception as e:
        logger.error(f"خطا در on_callback: {e}")
        try:
            await answer_callback_safely(callback, "خطا")
            if getattr(callback, "message", None):
                await callback.message.reply("⚠️ خطایی در پردازش دکمه رخ داد.")
        except Exception:
            pass




def is_channel_like_message(message: Message) -> bool:
    """
    کانال سفارشات/قفل اجباری نباید هیچ پاسخ خطا یا پیام راهنما از ربات بگیرد.
    این تابع هر پیام کانالی یا پیام بدون author معتبر را نادیده می‌گیرد تا ربات فقط در PV کاربران/ادمین پاسخ بدهد.
    """
    chat = getattr(message, "chat", None)
    author = getattr(message, "author", None)

    # اگر پیام sender/user معتبر ندارد، معمولاً پست کانال است.
    if author is None or getattr(author, "id", None) is None:
        return True

    chat_type = str(getattr(chat, "type", "") or "").lower()
    if chat_type in {"channel", "supergroup", "group"}:
        return True

    chat_username = str(getattr(chat, "username", "") or "").lstrip("@")
    blocked_channels = {
        (get_publish_channel() or "").lstrip("@"),
        (get_force_join_channel() or "").lstrip("@"),
    }
    if chat_username and chat_username in blocked_channels:
        return True

    return False

# ------------------------- پیام‌ها -------------------------

@bot.event
async def on_message(message: Message):
    try:
        # هیچ پیام/خطا/راهنمایی داخل کانال سفارشات یا گروه‌ها ارسال نشود؛
        # ربات فقط باید پست آگهی را هنگام publish به کانال بفرستد.
        if is_channel_like_message(message):
            return

        user_id = message.author.id
        text = message.text or ""

        cursor.execute(
            "INSERT OR IGNORE INTO users (user_id, username, full_name) VALUES (?, ?, ?)",
            (user_id, message.author.username or "", message.author.first_name or ""),
        )
        conn.commit()

        # ------------------------- پنل ادمین -------------------------
        if user_id == ADMIN_ID:
            if text in ["/panel", "🔧 پنل مدیریت", "🔙 برگشت به پنل"]:
                await message.reply(get_admin_panel_text(), components=get_admin_keyboard())
                return

            if text == "⚙️ تنظیمات پرداخت":
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if text == "🔐 قفل اجباری کانال":
                await message.reply(get_force_join_settings_text(), components=get_force_join_settings_keyboard())
                return

            if text in ["👥 کاربران", "/users"]:
                try:
                    cursor.execute("SELECT COUNT(*) AS c FROM users")
                    count = cursor.fetchone()["c"]
                    cursor.execute("SELECT user_id, username, full_name, created_at FROM users ORDER BY created_at DESC LIMIT 20")
                    users_list = cursor.fetchall()
                    msg = f"تعداد کل کاربران: {count}\n\n۲۰ کاربر اخیر:\n"
                    if users_list:
                        for u in users_list:
                            msg += f"{u['user_id']} | {u['username'] or 'بدون نام کاربری'} | {u['full_name'] or 'بدون نام'} | {u['created_at'] or 'نامشخص'}\n"
                    else:
                        msg += "هیچ کاربری یافت نشد."
                    await message.reply(msg, components=get_admin_keyboard())
                except Exception as e:
                    await message.reply(f"خطا در دریافت اطلاعات: {str(e)}", components=get_admin_keyboard())
                return

            if text in ["💰 واریزی‌ها", "/payments"]:
                try:
                    cursor.execute("SELECT SUM(amount) AS total FROM payments WHERE status IN ('approved', 'paid')")
                    total = cursor.fetchone()["total"] or 0
                    cursor.execute("SELECT user_id, ad_code, amount, method, status, description, created_at FROM payments ORDER BY created_at DESC LIMIT 20")
                    pays = cursor.fetchall()
                    msg = f"مجموع واریزی‌های تأییدشده: {money(total)} تومان\n\n۲۰ پرداخت اخیر:\n"
                    if pays:
                        for p in pays:
                            msg += f"{p['user_id']} | {p['ad_code'] or '-'} | {money(p['amount'] or 0)} تومان | {p['method'] or '-'} | {p['status'] or '-'} | {p['created_at']}\n"
                    else:
                        msg += "هیچ واریزی ثبت نشده است."
                    await message.reply(msg, components=get_admin_keyboard())
                except Exception as e:
                    await message.reply(f"خطا در دریافت واریزی‌ها: {str(e)}", components=get_admin_keyboard())
                return

            if text in ["📣 ارسال همگانی", "/broadcast"]:
                user_data[user_id] = {"step": "broadcast"}
                await message.reply("حالت ارسال همگانی فعال شد. پیام خود را بفرستید. برای لغو /cancel را ارسال کنید.")
                return

            if text in ["📮 تنظیم کانال ارسال", "/set_publish_channel"]:
                user_data[user_id] = {"step": "set_publish_channel"}
                await message.reply("آیدی کانال مقصد انتشار آگهی‌ها را ارسال کنید. مثال:\n@your_channel\n\nربات باید داخل این کانال ادمین باشد و اجازه ارسال پست داشته باشد. برای لغو /cancel بزنید.")
                return

            if text == "📢 تنظیم کانال قفل اجباری":
                user_data[user_id] = {"step": "set_force_join_channel"}
                await message.reply("آیدی کانالی که عضویت در آن اجباری باشد را ارسال کنید. مثال:\n@your_channel\n\nربات باید داخل این کانال ادمین باشد تا بتواند عضویت کاربران را بررسی کند. برای لغو /cancel بزنید.")
                return

            if text in ["📢 تنظیم متن کانال‌ها", "📢 تنظیم کانال‌ها", "/set_channels"]:
                user_data[user_id] = {"step": "set_channels"}
                await message.reply("متن نمایشی دکمه «کانال‌های ما» را ارسال کنید. این مورد فقط متن معرفی کانال‌هاست، نه کانال انتشار آگهی. برای لغو /cancel بزنید.")
                return

            if text in ["☎️ تنظیم پشتیبانی", "/set_support"]:
                user_data[user_id] = {"step": "set_support"}
                await message.reply("متن جدید پشتیبانی را ارسال کنید. برای لغو /cancel بزنید.")
                return

            if text == "🔑 تنظیم توکن پرداخت بله":
                user_data[user_id] = {"step": "set_bale_provider_token"}
                await message.reply("توکن/شناسه پرداخت بله را ارسال کنید. برای لغو /cancel بزنید.")
                return

            if text == "💳 تنظیم شماره کارت":
                user_data[user_id] = {"step": "set_card_number"}
                await message.reply("شماره کارت را بدون فاصله یا با فاصله ارسال کنید. برای لغو /cancel بزنید.")
                return

            if text == "👤 تنظیم نام صاحب کارت":
                user_data[user_id] = {"step": "set_card_owner"}
                await message.reply("نام صاحب کارت را ارسال کنید. برای لغو /cancel بزنید.")
                return

            if text == "💵 قیمت آگهی ساده":
                user_data[user_id] = {"step": "set_simple_price"}
                await message.reply("قیمت آگهی ساده را به تومان وارد کنید. مثال: 100000")
                return

            if text == "💎 قیمت آگهی ویژه":
                user_data[user_id] = {"step": "set_special_price"}
                await message.reply("قیمت آگهی ویژه را به تومان وارد کنید. مثال: 135000")
                return

            if text in ["🟢 روشن کردن پرداخت بله", "🔴 خاموش کردن پرداخت بله"]:
                set_setting("bale_payment_enabled", "0" if setting_bool("bale_payment_enabled", True) else "1")
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if text in ["🟢 روشن کردن کارت به کارت", "🔴 خاموش کردن کارت به کارت"]:
                set_setting("card_payment_enabled", "0" if setting_bool("card_payment_enabled", True) else "1")
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if text in ["🔒 فعال کردن الزام پرداخت", "🔓 غیرفعال کردن الزام پرداخت"]:
                set_setting("payment_required", "0" if setting_bool("payment_required", True) else "1")
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if text in ["🔒 روشن کردن قفل اجباری", "🔓 خاموش کردن قفل اجباری"]:
                set_setting("force_join_enabled", "0" if setting_bool("force_join_enabled", False) else "1")
                await message.reply(get_force_join_settings_text(), components=get_force_join_settings_keyboard())
                return

            admin_step = user_data.get(user_id, {}).get("step")
            if text == "/cancel" and admin_step in [
                "broadcast", "set_channels", "set_publish_channel", "set_force_join_channel", "set_support", "set_bale_provider_token", "set_card_number",
                "set_card_owner", "set_simple_price", "set_special_price"
            ]:
                user_data.pop(user_id, None)
                await message.reply("عملیات لغو شد.", components=get_admin_keyboard())
                return

            if admin_step == "set_publish_channel":
                channel = normalize_channel_username(text)
                if not channel or channel == "@" or len(channel) < 3:
                    await message.reply("❌ آیدی کانال معتبر نیست. مثال صحیح: @your_channel\nبرای لغو /cancel بزنید.")
                    return
                set_setting("publish_channel", channel)
                # دکمه «کانال‌های ما» همیشه همان کانال سفارشات/ارسال آگهی را نمایش می‌دهد.
                set_setting("channels_text", f"📢 کانال سفارشات ما:\n{channel}")
                user_data.pop(user_id, None)
                await message.reply(
                    f"کانال ارسال آگهی‌ها ذخیره شد: {channel}\n"
                    f"از این به بعد دکمه «📢 کانال‌های ما» هم همین کانال را نشان می‌دهد.\n"
                    f"اگر قفل اجباری روشن باشد و کانال جداگانه‌ای برای قفل تنظیم نکرده باشید، همین کانال برای بررسی عضویت استفاده می‌شود.\n"
                    f"حتماً ربات را در این کانال ادمین کنید.",
                    components=get_admin_keyboard(),
                )
                return

            if admin_step == "set_force_join_channel":
                channel = normalize_channel_username(text)
                if not channel or channel == "@" or len(channel) < 3:
                    await message.reply("❌ آیدی کانال معتبر نیست. مثال صحیح: @your_channel\nبرای لغو /cancel بزنید.")
                    return
                set_setting("force_join_channel", channel)
                user_data.pop(user_id, None)
                await message.reply(f"کانال قفل اجباری ذخیره شد: {channel}\nحتماً ربات را در این کانال ادمین کنید تا بررسی عضویت کار کند.\nاگر می‌خواهید قفل عضویت همان کانال سفارشات باشد، این مقدار را خالی/تنظیم‌نشده بگذارید.", components=get_force_join_settings_keyboard())
                await message.reply(get_force_join_settings_text(), components=get_force_join_settings_keyboard())
                return

            if admin_step == "set_channels":
                set_setting("channels_text", text)
                user_data.pop(user_id, None)
                await message.reply("متن دکمه کانال‌های ما به‌روزرسانی شد.", components=get_admin_keyboard())
                return

            if admin_step == "set_support":
                set_setting("support_text", text)
                user_data.pop(user_id, None)
                await message.reply("متن پشتیبانی به‌روزرسانی شد.", components=get_admin_keyboard())
                return

            if admin_step == "set_bale_provider_token":
                set_setting("bale_provider_token", text.strip())
                user_data.pop(user_id, None)
                await message.reply("توکن پرداخت بله ذخیره شد.", components=get_payment_settings_keyboard())
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if admin_step == "set_card_number":
                cleaned = text.strip()
                digits = cleaned.replace(" ", "").replace("-", "")
                if not digits.isdigit() or len(digits) < 12:
                    await message.reply("❌ شماره کارت معتبر نیست. دوباره ارسال کنید یا /cancel بزنید.")
                    return
                set_setting("card_number", cleaned)
                user_data.pop(user_id, None)
                await message.reply("شماره کارت ذخیره شد.", components=get_payment_settings_keyboard())
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if admin_step == "set_card_owner":
                set_setting("card_owner", text.strip())
                user_data.pop(user_id, None)
                await message.reply("نام صاحب کارت ذخیره شد.", components=get_payment_settings_keyboard())
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if admin_step in ["set_simple_price", "set_special_price"]:
                raw = text.replace(",", "").replace("٬", "").strip()
                if not raw.isdigit() or int(raw) <= 0:
                    await message.reply("❌ قیمت باید عدد مثبت باشد. مثال: 100000")
                    return
                set_setting("simple_ad_price" if admin_step == "set_simple_price" else "special_ad_price", raw)
                user_data.pop(user_id, None)
                await message.reply("قیمت ذخیره شد.", components=get_payment_settings_keyboard())
                await message.reply(get_payment_settings_text(), components=get_payment_settings_keyboard())
                return

            if admin_step == "broadcast":
                ok, fail = await broadcast_to_all_users(message, text if text else "")
                await message.reply(f"پیام همگانی ارسال شد.\nموفق: {ok}\nناموفق: {fail}", components=get_admin_keyboard())
                user_data.pop(user_id, None)
                return

        # ------------------------- قفل اجباری کانال برای کاربران -------------------------
        if user_id != ADMIN_ID:
            if text == "✅ بررسی عضویت":
                if await is_user_joined_force_channel(user_id):
                    await message.reply("✅ عضویت شما تأیید شد. حالا می‌توانید از ربات استفاده کنید.", components=get_main_keyboard())
                else:
                    await require_force_join(message, user_id)
                return
            if not await require_force_join(message, user_id):
                return

        # ------------------------- دستورات عمومی -------------------------
        if text == "/start":
            start_text = f"""🌟 به ربات آگهی خوش آمدید!

هزینه درج آگهی ساده: {money(get_ad_amount('simple'))} تومان
هزینه درج آگهی ویژه: {money(get_ad_amount('special'))} تومان
ــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــــ

برخلاف دیگر کانال‌های خرید و فروش:

- این کانال هیچ کمیسیونی بعد از فروش دریافت نمی‌کند.
- همچنین پست آگهی شما به صورت دائم تا زمان فروخته شدن داخل کانال قرار می‌گیرد.

لطفاً از دکمه‌های زیر استفاده کنید."""
            await message.reply(start_text, components=get_main_keyboard())
            return

        if text == "🏠 منوی اصلی":
            await message.reply("منوی اصلی", components=get_main_keyboard())
            return

        if text == "📞 پشتیبانی":
            await message.reply(get_setting("support_text", "پشتیبانی: @admin_agahi_bot"), components=get_main_keyboard())
            return

        if text == "📢 کانال‌های ما":
            channel = get_publish_channel()
            if channel:
                await message.reply(f"📢 کانال سفارشات ما:\n{channel}", components=get_main_keyboard())
            else:
                await message.reply("📢 کانال سفارشات هنوز توسط ادمین تنظیم نشده است.", components=get_main_keyboard())
            return

        if text in ["🔙 برگشت به منو", "❌ انصراف"]:
            user_data.pop(user_id, None)
            await message.reply("به منوی اصلی بازگشتید." if "برگشت" in text else "عملیات لغو شد.", components=get_main_keyboard())
            return

        # ------------------------- شروع ثبت آگهی -------------------------
        if text == "📝 ثبت آگهی":
            user_data[user_id] = {"step": "template"}
            await message.reply("نوع آگهی را انتخاب کنید:", components=get_template_keyboard())
            return

        if text.startswith("آگهی ساده"):
            user_data[user_id] = {"template_type": "simple", "step": "payment_method", "photos": []}
            await show_payment_method(user_id, message)
            return

        if text.startswith("آگهی ویژه"):
            user_data[user_id] = {"template_type": "special", "step": "payment_method", "photos": []}
            await show_payment_method(user_id, message)
            return

        data = user_data.get(user_id)
        if not data:
            await message.reply("لطفاً با دکمه «📝 ثبت آگهی» شروع کنید.", components=get_main_keyboard())
            return

        step = data.get("step")

        # ------------------------- پرداخت کاربر -------------------------
        if step == "payment_method":
            if text == "💳 پرداخت آنلاین بله":
                await send_bale_invoice(message, user_id, data, data["ad_code"])
                return
            if text == "🧾 کارت به کارت":
                await start_card_payment(message, user_id, data, data["ad_code"])
                return
            if text in ["🔙 برگشت به ویرایش", "🔙 برگشت به انتخاب نوع"]:
                data["step"] = "template"
                await message.reply("نوع آگهی را انتخاب کنید:", components=get_template_keyboard())
                return
            await message.reply("لطفاً روش پرداخت را از دکمه‌ها انتخاب کنید.", components=get_payment_method_keyboard(data.get("template_type", "simple")))
            return

        if step == "waiting_bale_payment":
            data["step"] = "payment_method"
            await message.reply("فاکتور پرداخت بله قبلاً ارسال شده است. اگر پرداخت نکردید، می‌توانید کارت‌به‌کارت را انتخاب کنید.", components=get_payment_method_keyboard(data.get("template_type", "simple")))
            return

        if step == "card_receipt":
            if text == "🔙 برگشت":
                data["step"] = "payment_method"
                await message.reply("روش پرداخت را انتخاب کنید:", components=get_payment_method_keyboard(data.get("template_type", "simple")))
                return
            await submit_card_receipt(message, user_id, data)
            return

        # ------------------------- دریافت عکس -------------------------
        if step == "photo":
            if getattr(message, "photos", None):
                new_photos = [p.file_id for p in message.photos]
                data["photos"].extend(new_photos)
                await message.reply(f"{len(new_photos)} عکس اضافه شد. مجموع: {len(data['photos'])} عکس.", components=get_photo_keyboard())
                return
            if getattr(message, "photo", None):
                photo = message.photo[-1] if isinstance(message.photo, list) else message.photo
                data["photos"].append(photo.file_id)
                await message.reply(f"۱ عکس اضافه شد. مجموع: {len(data['photos'])} عکس.", components=get_photo_keyboard())
                return
            if text == "✅ رفتن به مرحله بعد":
                if len(data.get("photos", [])) == 0:
                    await message.reply("حداقل یک عکس باید بفرستید.", components=get_photo_keyboard())
                    return
                data["step"] = "name"
                await message.reply("نام کالا را وارد کنید:", components=get_back_keyboard(include_skip=True))
                return
            if text == "🔙 برگشت":
                data["step"] = "template"
                await message.reply("نوع آگهی را انتخاب کنید:", components=get_template_keyboard())
                return
            await message.reply("لطفاً عکس بفرستید یا روی «✅ رفتن به مرحله بعد» کلیک کنید.", components=get_photo_keyboard())
            return

        # ------------------------- ویرایش فیلدها -------------------------
        edit_field = data.get("edit_field")
        if edit_field and step == edit_field:
            if text == "🔙 برگشت":
                data.pop("edit_field", None)
                data["step"] = "final"
                await show_final_preview(user_id, message)
                return
            if step == "name":
                data["type"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            elif step == "status":
                if text in ["نو", "در حد نو", "کارکرده"]:
                    data["status"] = text
                else:
                    await message.reply("لطفاً از دکمه‌ها استفاده کنید:", components=get_status_keyboard())
                    return
            elif step == "size":
                data["size"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            elif step == "city":
                data["city"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            elif step == "price":
                data["price"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            elif step == "phone":
                data["phone"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            elif step == "bale":
                if text == "⏭ رد کردن":
                    data["bale"] = "ذکر نشده"
                elif text.startswith("@") and len(text) > 1:
                    data["bale"] = text
                else:
                    await message.reply("❌ آیدی باید با @ شروع شود. یا روی رد کردن بزنید.", components=get_back_keyboard(include_skip=True))
                    return
            elif step == "desc":
                data["desc"] = "ذکر نشده" if text == "⏭ رد کردن" else text

            if step in ["phone", "bale"]:
                phone_val = data.get("phone", "")
                bale_val = data.get("bale", "")
                if (phone_val == "ذکر نشده" or phone_val == "") and (bale_val == "ذکر نشده" or bale_val == ""):
                    await message.reply("❌ حداقل یکی از بخش‌های «شماره تماس» یا «آیدی بله» باید پر شود.")
                    data["step"] = "phone"
                    data["edit_field"] = "phone"
                    await message.reply("📞 شماره تماس را وارد کنید:", components=get_phone_keyboard())
                    return
            data.pop("edit_field", None)
            data["step"] = "final"
            await show_final_preview(user_id, message)
            return

        # ------------------------- مراحل عادی -------------------------
        if step == "name":
            if text == "🔙 برگشت":
                data["step"] = "photo"
                await message.reply("برگشت به مرحله عکس.", components=get_photo_keyboard())
                return
            data["type"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            data["step"] = "status"
            await message.reply("وضعیت کالا را انتخاب کنید:", components=get_status_keyboard())
            return

        if step == "status":
            if text == "🔙 برگشت":
                data["step"] = "name"
                await message.reply("نام کالا را وارد کنید:", components=get_back_keyboard(include_skip=True))
                return
            if text in ["نو", "در حد نو", "کارکرده"]:
                data["status"] = text
                data["step"] = "size"
                await message.reply("سایز را وارد کنید:", components=get_back_keyboard(include_skip=True))
            else:
                await message.reply("لطفاً از دکمه‌ها استفاده کنید:", components=get_status_keyboard())
            return

        if step == "size":
            if text == "🔙 برگشت":
                data["step"] = "status"
                await message.reply("وضعیت کالا را انتخاب کنید:", components=get_status_keyboard())
                return
            data["size"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            data["step"] = "city"
            await message.reply("شهر را وارد کنید:", components=get_back_keyboard(include_skip=True))
            return

        if step == "city":
            if text == "🔙 برگشت":
                data["step"] = "size"
                await message.reply("سایز را وارد کنید:", components=get_back_keyboard(include_skip=True))
                return
            data["city"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            data["step"] = "price"
            await message.reply("قیمت را به تومان وارد کنید:", components=get_back_keyboard(include_skip=True))
            return

        if step == "price":
            if text == "🔙 برگشت":
                data["step"] = "city"
                await message.reply("شهر را وارد کنید:", components=get_back_keyboard(include_skip=True))
                return
            data["price"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            data["step"] = "phone"
            await message.reply("📞 شماره تماس را وارد کنید (در صورت خالی گذاشتن، حتما آیدی بله را وارد کنید):", components=get_phone_keyboard())
            return

        if step == "phone":
            if text == "🔙 برگشت":
                data["step"] = "price"
                await message.reply("قیمت را به تومان وارد کنید:", components=get_back_keyboard(include_skip=True))
                return
            data["phone"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            data["step"] = "bale"
            await message.reply("🆔 آیدی بله (با @ شروع می‌شود) را وارد کنید. در صورت وارد نکردن شماره تماس، این بخش الزامی است:", components=get_back_keyboard(include_skip=True))
            return

        if step == "bale":
            if text == "🔙 برگشت":
                data["step"] = "phone"
                await message.reply("📞 شماره تماس را وارد کنید:", components=get_phone_keyboard())
                return
            if text == "⏭ رد کردن":
                data["bale"] = "ذکر نشده"
            elif text.startswith("@") and len(text) > 1:
                data["bale"] = text
            else:
                await message.reply("❌ آیدی باید با @ شروع شود. یا روی رد کردن بزنید.", components=get_back_keyboard(include_skip=True))
                return
            phone_val = data.get("phone", "")
            bale_val = data.get("bale", "")
            if (phone_val == "ذکر نشده" or phone_val == "") and (bale_val == "ذکر نشده" or bale_val == ""):
                await message.reply("❌ حداقل یکی از بخش‌های «شماره تماس» یا «آیدی بله» باید پر شود.")
                data["step"] = "phone"
                await message.reply("📞 شماره تماس را وارد کنید:", components=get_phone_keyboard())
                return
            data["step"] = "desc"
            await message.reply("توضیحات را وارد کنید:", components=get_back_keyboard(include_skip=True))
            return

        if step == "desc":
            if text == "🔙 برگشت":
                data["step"] = "bale"
                await message.reply("🆔 آیدی بله را وارد کنید:", components=get_back_keyboard(include_skip=True))
                return
            data["desc"] = "ذکر نشده" if text == "⏭ رد کردن" else text
            ad_code = data.get("ad_code") or generate_ad_code()
            data["ad_code"] = ad_code
            data["step"] = "final"
            await show_final_preview(user_id, message)
            return

        if step == "final":
            if text == "✅ ثبت نهایی":
                ad_code = data.get("ad_code") or generate_ad_code()
                data["ad_code"] = ad_code
                row = get_ad(ad_code)
                is_paid = bool(data.get("payment_paid")) or (row is not None and row["payment_status"] == "paid") or not setting_bool("payment_required", True)
                if not is_paid:
                    await message.reply("❌ پرداخت این آگهی هنوز تأیید نشده است. لطفاً ابتدا پرداخت را انجام دهید.", components=get_main_keyboard())
                    user_data.pop(user_id, None)
                    return
                if not data.get("payment_method"):
                    data["payment_method"] = row["payment_method"] if row and row["payment_method"] else "ثبت شده"
                success = await send_to_admin_for_review(user_id, data, ad_code)
                await message.reply("آگهی شما برای تأیید به ادمین ارسال شد." if success else "خطا در ارسال آگهی.", components=get_main_keyboard())
                user_data.pop(user_id, None)
                return
            if text == "❌ انصراف":
                user_data.pop(user_id, None)
                await message.reply("عملیات لغو شد.", components=get_main_keyboard())
                return
            edit_map = {
                "📸 ویرایش عکس": ("photo", None, "لطفاً عکس‌های جدید را بفرستید. پس از اتمام روی «✅ رفتن به مرحله بعد» کلیک کنید.", get_photo_keyboard()),
                "📦 ویرایش نام کالا": ("name", "name", f"ویرایش نام کالا\nنام فعلی: {data.get('type')}\nنام جدید را وارد کنید:", get_back_keyboard(True)),
                "🛍 ویرایش وضعیت": ("status", "status", "ویرایش وضعیت کالا\nوضعیت جدید را انتخاب کنید:", get_status_keyboard()),
                "📏 ویرایش سایز": ("size", "size", f"ویرایش سایز\nسایز فعلی: {data.get('size')}\nسایز جدید را وارد کنید:", get_back_keyboard(True)),
                "📍 ویرایش شهر": ("city", "city", f"ویرایش شهر\nشهر فعلی: {data.get('city')}\nشهر جدید را وارد کنید:", get_back_keyboard(True)),
                "💰 ویرایش قیمت": ("price", "price", f"ویرایش قیمت\nقیمت فعلی: {data.get('price')}\nقیمت جدید را وارد کنید:", get_back_keyboard(True)),
                "📞 ویرایش تلفن": ("phone", "phone", f"ویرایش تلفن\nتلفن فعلی: {data.get('phone')}\nتلفن جدید را وارد کنید:", get_phone_keyboard()),
                "🆔 ویرایش بله": ("bale", "bale", f"ویرایش آیدی بله\nآیدی فعلی: {data.get('bale')}\nآیدی جدید را وارد کنید:", get_back_keyboard(True)),
                "📝 ویرایش توضیحات": ("desc", "desc", f"ویرایش توضیحات\nتوضیحات فعلی: {data.get('desc')}\nتوضیحات جدید را وارد کنید:", get_back_keyboard(True)),
            }
            if text in edit_map:
                next_step, edit_field, msg, kb = edit_map[text]
                data["step"] = next_step
                if edit_field:
                    data["edit_field"] = edit_field
                if text == "📸 ویرایش عکس":
                    data["photos"] = []
                await message.reply(msg, components=kb)
                return
            await message.reply("لطفاً از دکمه‌های کیبورد استفاده کنید.", components=get_edit_keyboard())
            return

        await message.reply("لطفاً از دکمه‌های راهنما استفاده کنید.", components=get_main_keyboard())

    except Exception as e:
        logger.error(f"خطا در on_message: {e}")
        # حتی در حالت خطا هم به کانال/گروه پیام نده؛ فقط در PV پاسخ خطای عمومی بده.
        try:
            if not is_channel_like_message(message):
                await message.reply("⚠️ خطایی رخ داد. دوباره تلاش کنید.")
        except Exception:
            pass


if __name__ == "__main__":
    print("Bale Bot Running")
    while True:
        try:
            bot.run()
        except Exception as e:
            print(f"Restarting... {e}")
            time.sleep(5)
