import json
import logging
import os
import re
import sqlite3
from datetime import datetime
from typing import Optional
from urllib.parse import quote

import requests
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReactionTypeEmoji, Update
from telegram.constants import ParseMode
from telegram.error import NetworkError
from telegram.request import HTTPXRequest
from telegram.ext import (
    Application,
    CallbackQueryHandler,
    CommandHandler,
    ContextTypes,
    MessageHandler,
    filters,
)


def load_env_file(path: str = ".env") -> None:
    if not os.path.exists(path):
        return
    try:
        with open(path, "r", encoding="utf-8") as env_file:
            for raw_line in env_file:
                line = raw_line.strip()
                if not line or line.startswith("#"):
                    continue
                if line.startswith("export "):
                    line = line[7:].strip()
                if "=" not in line:
                    continue
                key, value = line.split("=", 1)
                key = key.strip()
                value = value.strip().strip("'").strip('"')
                if key:
                    os.environ.setdefault(key, value)
    except Exception:
        pass


load_env_file()


def sanitize_broken_proxy_env() -> None:
    broken_values = {
        "http://127.0.0.1:9",
        "https://127.0.0.1:9",
        "socks5://127.0.0.1:9",
    }
    for key in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy"):
        value = os.environ.get(key, "").strip().lower()
        if value in broken_values:
            os.environ.pop(key, None)


sanitize_broken_proxy_env()


BOT_TOKEN = os.getenv("BOT_TOKEN", "8748018347:AAFOji4p79TASRaPXQ55ZoXGLOfdZ9UpUYA").strip()
DB_PATH = "domainjo.db"
ADMIN_DEFAULT_PASSWORD = os.getenv("ADMIN_PASSWORD", "123456")
MAX_ADMINS = 4
DEFAULT_ADMIN_IDS = [
    int(item.strip())
    for item in (os.getenv("ADMIN_IDS", "1122345454").split(","))
    if item.strip().isdigit()
]
OXAPAY_BASE_URL = "https://api.oxapay.com/v1"
CURRENCY_LABEL = "تومان"


logging.basicConfig(
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    level=logging.INFO,
    handlers=[
        logging.FileHandler("domainjo.log", encoding="utf-8"),
        logging.StreamHandler(),
    ],
)
logger = logging.getLogger(__name__)
http_session = requests.Session()
http_session.trust_env = False


def now_iso() -> str:
    return datetime.now().isoformat(timespec="seconds")


def db() -> sqlite3.Connection:
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn


def table_columns(conn: sqlite3.Connection, table_name: str) -> set[str]:
    rows = conn.execute(f"PRAGMA table_info({table_name})").fetchall()
    return {row["name"] for row in rows}


def ensure_column(conn: sqlite3.Connection, table_name: str, column_name: str, definition: str) -> None:
    if column_name not in table_columns(conn, table_name):
        conn.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {definition}")


def init_db() -> None:
    conn = db()
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS users (
            user_id INTEGER PRIMARY KEY,
            username TEXT,
            full_name TEXT,
            balance REAL DEFAULT 0.0,
            verified INTEGER DEFAULT 0,
            verify_file TEXT,
            card_file TEXT,
            created_at TEXT
        )
        """
    )
    ensure_column(conn, "users", "rial_balance", "REAL DEFAULT 0")
    ensure_column(conn, "users", "ton_balance", "REAL DEFAULT 0")
    ensure_column(conn, "users", "ton_wallet", "TEXT")
    ensure_column(conn, "users", "updated_at", "TEXT")

    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS admins (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER UNIQUE,
            username TEXT,
            added_at TEXT
        )
        """
    )
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS settings (
            key TEXT PRIMARY KEY,
            value TEXT
        )
        """
    )
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS ton_deposits (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            rial_amount REAL NOT NULL,
            ton_price REAL NOT NULL,
            ton_amount REAL NOT NULL,
            receipt_file TEXT,
            wallet_address TEXT,
            status TEXT DEFAULT 'pending',
            admin_note TEXT,
            created_at TEXT,
            updated_at TEXT
        )
        """
    )
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS ton_withdrawals (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            amount REAL NOT NULL,
            fee_percent REAL NOT NULL,
            fee_amount REAL NOT NULL,
            net_amount REAL NOT NULL,
            destination_address TEXT NOT NULL,
            comment TEXT,
            status TEXT DEFAULT 'pending',
            oxapay_track_id TEXT,
            tx_hash TEXT,
            admin_note TEXT,
            created_at TEXT,
            updated_at TEXT
        )
        """
    )
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS balance_logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            asset TEXT NOT NULL,
            change_amount REAL NOT NULL,
            reason TEXT,
            ref_type TEXT,
            ref_id INTEGER,
            created_at TEXT
        )
        """
    )

    defaults = {
        "admin_password": ADMIN_DEFAULT_PASSWORD,
        "support_username": os.getenv("SUPPORT_USERNAME", "@domainjo_support"),
        "ton_price": os.getenv("TON_PRICE", "220000"),
        "card_number": os.getenv("CARD_NUMBER", ""),
        "card_owner": os.getenv("CARD_OWNER", ""),
        "merchant_ton_wallet": os.getenv("MERCHANT_TON_WALLET", ""),
        "withdrawal_auto": os.getenv("WITHDRAWAL_AUTO", "0"),
        "max_auto_withdraw_ton": os.getenv("MAX_AUTO_WITHDRAW_TON", "10"),
        "withdrawal_fee_percent": os.getenv("WITHDRAWAL_FEE_PERCENT", "1"),
        "min_withdraw_ton": os.getenv("MIN_WITHDRAW_TON", "0.5"),
        "oxapay_general_api_key": os.getenv("OXAPAY_GENERAL_API_KEY", os.getenv("OXAPAY_API_KEY", "")),
        "oxapay_payout_api_key": os.getenv("OXAPAY_PAYOUT_API_KEY", os.getenv("OXAPAY_API_KEY", "")),
    }
    for key, value in defaults.items():
        conn.execute("INSERT OR IGNORE INTO settings(key, value) VALUES(?, ?)", (key, value))

    if "balance" in table_columns(conn, "users"):
        conn.execute(
            """
            UPDATE users
            SET rial_balance = balance
            WHERE COALESCE(rial_balance, 0) = 0 AND COALESCE(balance, 0) > 0
            """
        )

    for admin_id in DEFAULT_ADMIN_IDS:
        conn.execute(
            "INSERT OR IGNORE INTO admins(user_id, username, added_at) VALUES(?, ?, ?)",
            (admin_id, str(admin_id), now_iso()),
        )

    conn.commit()
    conn.close()


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


def set_setting(key: str, value: object) -> None:
    conn = db()
    conn.execute("INSERT OR REPLACE INTO settings(key, value) VALUES(?, ?)", (key, str(value)))
    conn.commit()
    conn.close()


def get_card_slots() -> list[dict]:
    raw = get_setting("card_slots", "").strip()
    cards: list[dict] = []
    if raw:
        try:
            parsed = json.loads(raw)
            if isinstance(parsed, list):
                for item in parsed[:3]:
                    if isinstance(item, dict):
                        cards.append(
                            {
                                "number": str(item.get("number", "")).strip(),
                                "owner": str(item.get("owner", "")).strip(),
                            }
                        )
        except Exception:
            cards = []
    if not cards:
        legacy_number = get_setting("card_number", "").strip()
        legacy_owner = get_setting("card_owner", "").strip()
        if legacy_number or legacy_owner:
            cards.append({"number": legacy_number, "owner": legacy_owner})
    while len(cards) < 3:
        cards.append({"number": "", "owner": ""})
    return cards[:3]


def save_card_slots(cards: list[dict]) -> None:
    normalized = []
    for item in cards[:3]:
        normalized.append(
            {
                "number": str(item.get("number", "")).strip(),
                "owner": str(item.get("owner", "")).strip(),
            }
        )
    while len(normalized) < 3:
        normalized.append({"number": "", "owner": ""})
    set_setting("card_slots", json.dumps(normalized, ensure_ascii=False))
    set_setting("card_number", normalized[0]["number"])
    set_setting("card_owner", normalized[0]["owner"])


def set_card_slot(slot_index: int, number: Optional[str] = None, owner: Optional[str] = None) -> list[dict]:
    cards = get_card_slots()
    if 0 <= slot_index < 3:
        if number is not None:
            cards[slot_index]["number"] = number.strip()
        if owner is not None:
            cards[slot_index]["owner"] = owner.strip()
        save_card_slots(cards)
    return cards


def get_active_cards() -> list[dict]:
    return [card for card in get_card_slots() if card.get("number") and card.get("owner")]


def format_cards_text() -> str:
    cards = get_card_slots()
    lines = []
    for idx, card in enumerate(cards, start=1):
        lines.append(f"کارت {idx}:")
        lines.append(f"شماره کارت: {card['number'] or '❌ تنظیم نشده'}")
        lines.append(f"صاحب کارت: {card['owner'] or '❌ تنظیم نشده'}")
    return "\n".join(lines)


def setting_bool(key: str) -> bool:
    return get_setting(key, "0").strip().lower() in {"1", "true", "yes", "on"}


def parse_number(value: object) -> Optional[float]:
    if value is None:
        return None
    if isinstance(value, (int, float)):
        return float(value)
    text = str(value).strip()
    if not text:
        return None
    text = (
        text.replace(",", "")
        .replace(" ", "")
        .replace("تومان", "")
        .replace("تون", "")
        .replace("ton", "")
    )
    match = re.search(r"-?\d+(?:\.\d+)?", text)
    if not match:
        return None
    try:
        return float(match.group(0))
    except ValueError:
        return None


def format_money(amount: object) -> str:
    value = parse_number(amount) or 0
    return f"{int(round(value)):,} {CURRENCY_LABEL}"


def format_ton(amount: object) -> str:
    value = parse_number(amount) or 0
    return f"{value:.4f} TON"


def ton_to_nano(amount: float) -> int:
    return int(round(amount * 1_000_000_000))


def build_tonkeeper_link(address: str, amount: Optional[float] = None, comment: str = "") -> str:
    query_parts = []
    if amount is not None and amount > 0:
        query_parts.append(f"amount={ton_to_nano(amount)}")
    if comment:
        query_parts.append(f"text={quote(comment)}")
    suffix = f"?{'&'.join(query_parts)}" if query_parts else ""
    return f"https://app.tonkeeper.com/transfer/{quote(address)}{suffix}"


def validate_ton_address(address: str) -> bool:
    address = (address or "").strip()
    if not address:
        return False
    if re.fullmatch(r"[UEkQ0-9A-Za-z_-]{36,80}", address):
        return True
    if ":" in address and len(address) >= 48:
        return True
    return False


def get_user(user_id: int) -> Optional[sqlite3.Row]:
    conn = db()
    row = conn.execute("SELECT * FROM users WHERE user_id=?", (user_id,)).fetchone()
    conn.close()
    return row


def upsert_user(user_id: int, username: Optional[str], full_name: Optional[str]) -> None:
    conn = db()
    conn.execute(
        """
        INSERT INTO users(user_id, username, full_name, created_at, updated_at)
        VALUES(?, ?, ?, ?, ?)
        ON CONFLICT(user_id) DO UPDATE SET
            username=excluded.username,
            full_name=excluded.full_name,
            updated_at=excluded.updated_at
        """,
        (user_id, username, full_name, now_iso(), now_iso()),
    )
    conn.commit()
    conn.close()


def is_admin(user_id: int) -> bool:
    conn = db()
    row = conn.execute("SELECT 1 FROM admins WHERE user_id=?", (user_id,)).fetchone()
    conn.close()
    return row is not None


def admin_count() -> int:
    conn = db()
    count = conn.execute("SELECT COUNT(*) AS c FROM admins").fetchone()["c"]
    conn.close()
    return int(count)


def add_admin(user_id: int, username: str) -> bool:
    if not is_admin(user_id) and admin_count() >= MAX_ADMINS:
        return False
    conn = db()
    conn.execute(
        "INSERT OR IGNORE INTO admins(user_id, username, added_at) VALUES(?, ?, ?)",
        (user_id, username or str(user_id), now_iso()),
    )
    conn.commit()
    conn.close()
    return True


def get_all_admins() -> list[sqlite3.Row]:
    conn = db()
    rows = conn.execute("SELECT user_id, username FROM admins ORDER BY id").fetchall()
    conn.close()
    return rows


def remove_admin(user_id: int) -> None:
    conn = db()
    conn.execute("DELETE FROM admins WHERE user_id=?", (user_id,))
    conn.commit()
    conn.close()


def set_user_wallet(user_id: int, wallet: str) -> None:
    conn = db()
    conn.execute("UPDATE users SET ton_wallet=?, updated_at=? WHERE user_id=?", (wallet, now_iso(), user_id))
    conn.commit()
    conn.close()


def set_verified(user_id: int, verified: int) -> None:
    conn = db()
    conn.execute("UPDATE users SET verified=?, updated_at=? WHERE user_id=?", (verified, now_iso(), user_id))
    conn.commit()
    conn.close()


def log_balance_change(user_id: int, asset: str, amount: float, reason: str, ref_type: str = "", ref_id: Optional[int] = None) -> None:
    conn = db()
    conn.execute(
        """
        INSERT INTO balance_logs(user_id, asset, change_amount, reason, ref_type, ref_id, created_at)
        VALUES(?, ?, ?, ?, ?, ?, ?)
        """,
        (user_id, asset, amount, reason, ref_type, ref_id, now_iso()),
    )
    conn.commit()
    conn.close()


def adjust_rial_balance(user_id: int, amount: float, reason: str, ref_type: str = "", ref_id: Optional[int] = None) -> None:
    conn = db()
    conn.execute("UPDATE users SET rial_balance = COALESCE(rial_balance, 0) + ?, updated_at=? WHERE user_id=?", (amount, now_iso(), user_id))
    conn.commit()
    conn.close()
    log_balance_change(user_id, "IRR", amount, reason, ref_type, ref_id)


def adjust_ton_balance(user_id: int, amount: float, reason: str, ref_type: str = "", ref_id: Optional[int] = None) -> None:
    conn = db()
    conn.execute("UPDATE users SET ton_balance = COALESCE(ton_balance, 0) + ?, updated_at=? WHERE user_id=?", (amount, now_iso(), user_id))
    conn.commit()
    conn.close()
    log_balance_change(user_id, "TON", amount, reason, ref_type, ref_id)


def get_stats() -> dict:
    conn = db()
    stats = {
        "users": conn.execute("SELECT COUNT(*) AS c FROM users").fetchone()["c"],
        "verified": conn.execute("SELECT COUNT(*) AS c FROM users WHERE verified=1").fetchone()["c"],
        "pending_verify": conn.execute(
            "SELECT COUNT(*) AS c FROM users WHERE verified=0 AND verify_file IS NOT NULL AND card_file IS NOT NULL"
        ).fetchone()["c"],
        "pending_withdrawals": conn.execute(
            "SELECT COUNT(*) AS c FROM ton_withdrawals WHERE status IN ('pending', 'queued')"
        ).fetchone()["c"],
        "sum_ton": conn.execute("SELECT COALESCE(SUM(ton_balance), 0) AS c FROM users").fetchone()["c"],
        "sum_rial": conn.execute("SELECT COALESCE(SUM(rial_balance), 0) AS c FROM users").fetchone()["c"],
    }
    conn.close()
    return stats


def get_all_users(limit: int = 50) -> list[sqlite3.Row]:
    conn = db()
    rows = conn.execute(
        """
        SELECT user_id, username, full_name, verified, rial_balance, ton_balance, ton_wallet, created_at
        FROM users
        ORDER BY created_at DESC
        LIMIT ?
        """,
        (limit,),
    ).fetchall()
    conn.close()
    return rows


def get_pending_verifications() -> list[sqlite3.Row]:
    conn = db()
    rows = conn.execute(
        """
        SELECT user_id, username, full_name, verify_file, card_file
        FROM users
        WHERE verified=0 AND verify_file IS NOT NULL AND card_file IS NOT NULL
        ORDER BY created_at DESC
        """
    ).fetchall()
    conn.close()
    return rows


def create_deposit_request(user_id: int, rial_amount: float, wallet_address: str, receipt_file: str) -> int:
    ton_price = parse_number(get_setting("ton_price", "220000")) or 220000
    ton_amount = rial_amount / ton_price if ton_price > 0 else 0
    conn = db()
    cursor = conn.execute(
        """
        INSERT INTO ton_deposits(user_id, rial_amount, ton_price, ton_amount, receipt_file, wallet_address, status, created_at, updated_at)
        VALUES(?, ?, ?, ?, ?, ?, 'pending', ?, ?)
        """,
        (user_id, rial_amount, ton_price, ton_amount, receipt_file, wallet_address, now_iso(), now_iso()),
    )
    deposit_id = cursor.lastrowid
    conn.commit()
    conn.close()
    return int(deposit_id)


def get_deposit_request(deposit_id: int) -> Optional[sqlite3.Row]:
    conn = db()
    row = conn.execute("SELECT * FROM ton_deposits WHERE id=?", (deposit_id,)).fetchone()
    conn.close()
    return row


def get_pending_deposits() -> list[sqlite3.Row]:
    conn = db()
    rows = conn.execute(
        """
        SELECT d.*, u.full_name, u.username
        FROM ton_deposits d
        LEFT JOIN users u ON u.user_id = d.user_id
        WHERE d.status='pending'
        ORDER BY d.id DESC
        """
    ).fetchall()
    conn.close()
    return rows


def approve_deposit(deposit_id: int, admin_note: str = "") -> Optional[sqlite3.Row]:
    deposit = get_deposit_request(deposit_id)
    if not deposit or deposit["status"] != "pending":
        return None
    adjust_rial_balance(deposit["user_id"], float(deposit["rial_amount"]), "تایید افزایش موجودی", "deposit", deposit_id)
    conn = db()
    conn.execute(
        "UPDATE ton_deposits SET status='approved', admin_note=?, updated_at=? WHERE id=?",
        (admin_note, now_iso(), deposit_id),
    )
    conn.commit()
    conn.close()
    return get_deposit_request(deposit_id)


def reject_deposit(deposit_id: int, admin_note: str = "") -> Optional[sqlite3.Row]:
    deposit = get_deposit_request(deposit_id)
    if not deposit or deposit["status"] != "pending":
        return None
    conn = db()
    conn.execute(
        "UPDATE ton_deposits SET status='rejected', admin_note=?, updated_at=? WHERE id=?",
        (admin_note, now_iso(), deposit_id),
    )
    conn.commit()
    conn.close()
    return get_deposit_request(deposit_id)


def create_withdrawal_request(
    user_id: int,
    amount: float,
    destination_address: str,
    comment: str,
    status: str = "pending",
) -> int:
    fee_percent = parse_number(get_setting("withdrawal_fee_percent", "1")) or 1
    fee_amount = round(amount * fee_percent / 100, 9)
    net_amount = round(max(amount - fee_amount, 0), 9)
    conn = db()
    cursor = conn.execute(
        """
        INSERT INTO ton_withdrawals(
            user_id, amount, fee_percent, fee_amount, net_amount,
            destination_address, comment, status, created_at, updated_at
        ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (user_id, amount, fee_percent, fee_amount, net_amount, destination_address, comment, status, now_iso(), now_iso()),
    )
    withdrawal_id = cursor.lastrowid
    conn.commit()
    conn.close()
    return int(withdrawal_id)


def get_withdrawal(withdrawal_id: int) -> Optional[sqlite3.Row]:
    conn = db()
    row = conn.execute("SELECT * FROM ton_withdrawals WHERE id=?", (withdrawal_id,)).fetchone()
    conn.close()
    return row


def get_pending_withdrawals() -> list[sqlite3.Row]:
    conn = db()
    rows = conn.execute(
        """
        SELECT w.*, u.full_name, u.username
        FROM ton_withdrawals w
        LEFT JOIN users u ON u.user_id = w.user_id
        WHERE w.status IN ('pending', 'queued')
        ORDER BY w.id DESC
        """
    ).fetchall()
    conn.close()
    return rows


def set_withdrawal_status(withdrawal_id: int, status: str, admin_note: str = "", track_id: str = "", tx_hash: str = "") -> None:
    conn = db()
    conn.execute(
        """
        UPDATE ton_withdrawals
        SET status=?, admin_note=?, oxapay_track_id=COALESCE(NULLIF(?, ''), oxapay_track_id),
            tx_hash=COALESCE(NULLIF(?, ''), tx_hash), updated_at=?
        WHERE id=?
        """,
        (status, admin_note, track_id, tx_hash, now_iso(), withdrawal_id),
    )
    conn.commit()
    conn.close()


def refund_withdrawal(withdrawal_id: int, admin_note: str = "") -> Optional[sqlite3.Row]:
    withdrawal = get_withdrawal(withdrawal_id)
    if not withdrawal or withdrawal["status"] not in {"pending", "queued"}:
        return None
    adjust_ton_balance(withdrawal["user_id"], float(withdrawal["amount"]), "برگشت برداشت", "withdrawal", withdrawal_id)
    set_withdrawal_status(withdrawal_id, "rejected", admin_note)
    return get_withdrawal(withdrawal_id)


def oxapay_general_key() -> str:
    return get_setting("oxapay_general_api_key", "").strip()


def oxapay_payout_key() -> str:
    return get_setting("oxapay_payout_api_key", "").strip()


def payout_auto_ready() -> bool:
    return bool(oxapay_payout_key())


def oxapay_get_balance() -> dict:
    api_key = oxapay_general_key()
    if not api_key:
        return {"ok": False, "message": "API Key تنظیم نشده است."}
    try:
        response = http_session.get(
            f"{OXAPAY_BASE_URL}/general/account/balance",
            headers={"general_api_key": api_key, "Content-Type": "application/json"},
            params={"currency": "TON"},
            timeout=20,
        )
        data = response.json()
        if response.status_code != 200 or data.get("status") != 200:
            message = (
                data.get("error", {}).get("message")
                or data.get("message")
                or response.text
                or "خطای نامشخص"
            )
            return {"ok": False, "message": message}
        ton_balance = (data.get("data") or {}).get("TON", 0)
        return {"ok": True, "ton": float(ton_balance)}
    except Exception as exc:
        return {"ok": False, "message": str(exc)}


def oxapay_create_payout(address: str, amount: float, memo: str = "", description: str = "") -> dict:
    api_key = oxapay_payout_key()
    if not api_key:
        return {"ok": False, "message": "Payout API Key تنظیم نشده است."}
    payload = {
        "address": address,
        "currency": "TON",
        "amount": round(amount, 9),
        "network": "TON",
    }
    if memo:
        payload["memo"] = memo[:120]
    if description:
        payload["description"] = description[:250]
    try:
        response = http_session.post(
            f"{OXAPAY_BASE_URL}/payout",
            headers={"payout_api_key": api_key, "Content-Type": "application/json"},
            data=json.dumps(payload),
            timeout=25,
        )
        data = response.json()
        if response.status_code != 200 or data.get("status") != 200:
            message = (
                data.get("error", {}).get("message")
                or data.get("message")
                or response.text
                or "خطای نامشخص"
            )
            return {"ok": False, "message": message, "raw": data}
        details = data.get("data") or {}
        return {
            "ok": True,
            "track_id": str(details.get("track_id", "")),
            "status": str(details.get("status", "processing")),
            "raw": data,
        }
    except Exception as exc:
        return {"ok": False, "message": str(exc)}


def try_auto_withdraw(withdrawal_id: int) -> dict:
    withdrawal = get_withdrawal(withdrawal_id)
    if not withdrawal:
        return {"ok": False, "message": "برداشت پیدا نشد."}
    result = oxapay_create_payout(
        address=withdrawal["destination_address"],
        amount=float(withdrawal["net_amount"]),
        memo=withdrawal["comment"] or "",
        description=f"Telegram withdrawal #{withdrawal_id}",
    )
    if not result["ok"]:
        set_withdrawal_status(withdrawal_id, "pending", result["message"])
        return result
    set_withdrawal_status(withdrawal_id, "processing", "ارسال خودکار به OxaPay", result.get("track_id", ""))
    return result


def admin_panel_text() -> str:
    stats = get_stats()
    ton_price = parse_number(get_setting("ton_price", "220000")) or 220000
    cards_text = format_cards_text()
    auto_withdraw = "✅ فعال" if payout_auto_ready() else "❌ تنظیم نشده"
    max_auto = parse_number(get_setting("max_auto_withdraw_ton", "10")) or 10
    fee_percent = parse_number(get_setting("withdrawal_fee_percent", "1")) or 1
    min_withdraw = parse_number(get_setting("min_withdraw_ton", "0.5")) or 0.5
    oxapay = oxapay_get_balance()
    if oxapay["ok"]:
        oxapay_text = f"✅ {format_ton(oxapay['ton'])}"
    else:
        oxapay_text = f"❌ خطا در دریافت موجودی\nخطا: {oxapay['message']}"
    return (
        "🔧 پنل مدیریت\n\n"
        "📊 آمار ربات:\n"
        f"👥 کل کاربران: {int(stats['users'])}\n"
        f"✅ تأیید شده: {int(stats['verified'])}\n"
        f"⏳ در انتظار تأیید هویت: {int(stats['pending_verify'])}\n"
        f"🏦 در انتظار تأیید برداشت: {int(stats['pending_withdrawals'])}\n"
        f"💎 مجموع تون کاربران: {format_ton(stats['sum_ton'])}\n"
        f"💰 مجموع تومان کاربران: {format_money(stats['sum_rial'])}\n\n"
        f"💵 قیمت تون: {format_money(ton_price)}\n\n"
        "💳 تنظیمات کارت:\n"
        f"{cards_text}\n\n"
        "🏦 موجودی کیف پول OxaPay:\n"
        f"{oxapay_text}\n\n"
        "⚙️ تنظیمات برداشت:\n"
        f"برداشت خودکار: {auto_withdraw}\n"
        f"حداکثر برداشت خودکار: {format_ton(max_auto)}\n"
        f"کارمزد: {fee_percent:.2f}%\n"
        f"حداقل برداشت: {format_ton(min_withdraw)}\n\n"
        "📋 دستورات مدیریت:\n"
        "/setprice [قیمت] - تنظیم قیمت تون\n"
        "/setcard [slot] [شماره] - تنظیم شماره کارت\n"
        "/setowner [slot] [نام] - تنظیم صاحب کارت\n"
        "/setwallet [آدرس] - تنظیم ولت اصلی\n"
        "/setoxapay [general|payout] - تنظیم API\n"
        "/auto_withdraw [on|off] - فقط نمایش وضعیت قدیمی\n"
        "/pending - درخواست‌های احراز هویت\n"
        "/pending_deposits - درخواست‌های افزایش موجودی\n"
        "/pending_withdrawals - برداشت‌های در انتظار\n"
        "/stats - آمار کامل\n"
        "/broadcast [متن] - پیام همگانی\n"
        "/user [id] - اطلاعات کاربر\n"
        "/addton [id] [مقدار] - افزایش تون\n"
        "/addrial [id] [مقدار] - افزایش تومان"
    )


def sbtn(text: str, style: Optional[str] = None, **kwargs) -> InlineKeyboardButton:
    api_kwargs = kwargs.pop("api_kwargs", None) or {}
    if style:
        api_kwargs["style"] = style
    return InlineKeyboardButton(text, api_kwargs=api_kwargs or None, **kwargs)


def kb_back(target: str = "main_menu", label: str = "🔙 بازگشت") -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup([[sbtn(label, callback_data=target, style="danger")]])


def kb_main(user_id: Optional[int] = None) -> InlineKeyboardMarkup:
    rows = [
        [
            sbtn("👤 حساب کاربری", callback_data="my_account", style="primary"),
            sbtn("🛒 خرید TON", callback_data="buy_ton", style="success"),
        ],
        [
            sbtn("💰 افزایش موجودی", callback_data="add_balance", style="success"),
            sbtn("📤 انتقال TON", callback_data="transfer_ton", style="primary"),
        ],
        [
            sbtn("🎧 پشتیبانی", callback_data="support", style="primary"),
        ],
    ]
    if user_id and is_admin(user_id):
        rows.append([sbtn("⚙️ پنل ادمین", callback_data="admin_panel", style="primary")])
    return InlineKeyboardMarkup(rows)


def kb_account(user: sqlite3.Row) -> InlineKeyboardMarkup:
    wallet = user["ton_wallet"] or ""
    rows = [
        [
            sbtn("✏️ ثبت/ویرایش ولت", callback_data="set_wallet", style="primary"),
            sbtn("🔄 بروزرسانی", callback_data="my_account", style="success"),
        ]
    ]
    if wallet:
        rows.append([InlineKeyboardButton("📲 باز کردن در Tonkeeper", url=build_tonkeeper_link(wallet))])
    rows.append([sbtn("🔙 بازگشت", callback_data="main_menu", style="danger")])
    return InlineKeyboardMarkup(rows)


def kb_admin() -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup(
        [
            [
                sbtn("📊 بروزرسانی پنل", callback_data="admin_panel", style="primary"),
                sbtn("✅ احرازها", callback_data="adm_verify", style="success"),
            ],
            [
                sbtn("💰 شارژهای در انتظار", callback_data="adm_deposits", style="success"),
                sbtn("📤 برداشت‌های در انتظار", callback_data="adm_withdrawals", style="primary"),
            ],
            [
                sbtn("👥 کاربران", callback_data="adm_users", style="primary"),
                sbtn("💸 شارژ دستی", callback_data="adm_charge", style="success"),
            ],
            [sbtn("💳 تنظیم کارت‌ها", callback_data="adm_cards", style="primary")],
            [sbtn("🔙 بازگشت به منو", callback_data="main_menu", style="danger")],
        ]
    )


async def notify_admins_about_verification(
    ctx: ContextTypes.DEFAULT_TYPE,
    user_id: int,
    full_name: str,
    verify_file_id: str,
    card_file_id: str,
) -> None:
    review_kb = InlineKeyboardMarkup(
        [
            [
                sbtn("✅ تایید", callback_data=f"approve_verify_{user_id}", style="success"),
                sbtn("❌ رد", callback_data=f"reject_verify_{user_id}", style="danger"),
            ]
        ]
    )
    for admin in get_all_admins():
        try:
            await ctx.bot.send_photo(
                admin["user_id"],
                verify_file_id,
                caption=f"🆕 درخواست احراز هویت\n\n👤 {full_name}\n🆔 `{user_id}`",
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=review_kb,
            )
            await ctx.bot.send_photo(
                admin["user_id"],
                card_file_id,
                caption=f"💳 تصویر کارت بانکی کاربر `{user_id}`",
                parse_mode=ParseMode.MARKDOWN,
            )
        except Exception:
            logger.exception("Failed to notify admin about verification")


async def notify_admins_about_deposit(ctx: ContextTypes.DEFAULT_TYPE, deposit_id: int) -> None:
    deposit = get_deposit_request(deposit_id)
    if not deposit:
        return
    user = get_user(deposit["user_id"])
    caption = (
        "💰 درخواست افزایش موجودی جدید\n\n"
        f"👤 کاربر: {user['full_name'] if user else deposit['user_id']}\n"
        f"🆔 `{deposit['user_id']}`\n"
        f"💵 مبلغ: {format_money(deposit['rial_amount'])}\n"
        f"💎 معادل تقریبی: {format_ton(deposit['ton_amount'])}\n"
        f"🏷 قیمت TON: {format_money(deposit['ton_price'])}"
    )
    reply_markup = InlineKeyboardMarkup(
        [
            [
                sbtn("✅ تایید", callback_data=f"approve_deposit_{deposit_id}", style="success"),
                sbtn("❌ رد", callback_data=f"reject_deposit_{deposit_id}", style="danger"),
            ]
        ]
    )
    for admin in get_all_admins():
        try:
            await ctx.bot.send_photo(
                admin["user_id"],
                deposit["receipt_file"],
                caption=caption,
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=reply_markup,
            )
        except Exception:
            logger.exception("Failed to notify admin about deposit")


async def notify_admins_about_withdrawal(ctx: ContextTypes.DEFAULT_TYPE, withdrawal_id: int) -> None:
    withdrawal = get_withdrawal(withdrawal_id)
    if not withdrawal:
        return
    user = get_user(withdrawal["user_id"])
    text = (
        "📤 درخواست انتقال TON\n\n"
        f"👤 کاربر: {user['full_name'] if user else withdrawal['user_id']}\n"
        f"🆔 `{withdrawal['user_id']}`\n"
        f"💎 مبلغ درخواستی: {format_ton(withdrawal['amount'])}\n"
        f"💸 کارمزد: {format_ton(withdrawal['fee_amount'])}\n"
        f"✅ مبلغ خالص: {format_ton(withdrawal['net_amount'])}\n"
        f"📬 آدرس: `{withdrawal['destination_address']}`\n"
        f"📝 کامنت: {withdrawal['comment'] or 'ندارد'}\n"
        "این درخواست از سمت سرور قابل ارسال است و نیازی به ورود کاربر به Tonkeeper ندارد."
    )
    tonkeeper_link = build_tonkeeper_link(
        withdrawal["destination_address"],
        float(withdrawal["net_amount"]),
        withdrawal["comment"] or "",
    )
    reply_markup = InlineKeyboardMarkup(
        [
            [
                sbtn("✅ تایید", callback_data=f"approve_withdraw_{withdrawal_id}", style="success"),
                sbtn("❌ رد", callback_data=f"reject_withdraw_{withdrawal_id}", style="danger"),
            ],
            [InlineKeyboardButton("📲 Tonkeeper", url=tonkeeper_link)],
        ]
    )
    for admin in get_all_admins():
        try:
            await ctx.bot.send_message(
                admin["user_id"],
                text,
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=reply_markup,
            )
        except Exception:
            logger.exception("Failed to notify admin about withdrawal")


async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    user = update.effective_user
    upsert_user(user.id, user.username, user.full_name)
    try:
        await update.message.set_reaction([ReactionTypeEmoji("👋")])
    except Exception:
        pass
    text = (
        f"سلام {user.first_name} عزیز 👋\n\n"
        "به ربات خرید و انتقال TON خوش آمدی.\n"
        "از اینجا می‌تونی حساب کاربری‌ات را ببینی، موجودی ریالی شارژ کنی، TON بخری و درخواست انتقال ثبت کنی."
    )
    await update.message.reply_text(text, reply_markup=kb_main(user.id))


async def cb_main_menu(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    await q.edit_message_text("🏠 منوی اصلی", reply_markup=kb_main(update.effective_user.id))


async def cb_my_account(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    user_obj = update.effective_user
    upsert_user(user_obj.id, user_obj.username, user_obj.full_name)
    user = get_user(user_obj.id)
    verified = "✅ تایید شده" if user and user["verified"] else "❌ احراز نشده"
    created_at = user["created_at"][:10] if user and user["created_at"] else "-"
    text = (
        "👤 حساب کاربری\n\n"
        f"نام: {user_obj.full_name}\n"
        f"آیدی: `{user_obj.id}`\n"
        f"احراز هویت: {verified}\n"
        f"موجودی تومان: {format_money(user['rial_balance'] if user else 0)}\n"
        f"موجودی TON: {format_ton(user['ton_balance'] if user else 0)}\n"
        f"ولت ثبت‌شده: `{(user['ton_wallet'] or 'ثبت نشده') if user else 'ثبت نشده'}`\n"
        f"تاریخ عضویت: {created_at}"
    )
    await q.edit_message_text(text, parse_mode=ParseMode.MARKDOWN, reply_markup=kb_account(user))


async def cb_set_wallet(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    ctx.user_data["state"] = "waiting_wallet_address"
    await q.edit_message_text(
        "📥 آدرس ولت TON خودت را بفرست.\n\nمثال:\n`UQ...`",
        parse_mode=ParseMode.MARKDOWN,
        reply_markup=kb_back("my_account"),
    )


async def cb_add_balance(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    user = get_user(update.effective_user.id)
    if not user or not user["verified"]:
        ctx.user_data["state"] = "waiting_verify_selfie"
        await q.edit_message_text(
            "🛡 برای افزایش موجودی باید احراز هویت انجام شده باشد.\n\n"
            "لطفاً عکس سلفی احراز هویت را ارسال کن.",
            reply_markup=kb_back(),
        )
        return
    active_cards = get_active_cards()
    if not active_cards:
        await q.edit_message_text(
            "⚠️ اطلاعات کارت هنوز توسط ادمین تنظیم نشده است.",
            reply_markup=kb_back(),
        )
        return
    ctx.user_data["state"] = "waiting_rial_topup_amount"
    card_lines = []
    for idx, card in enumerate(active_cards, start=1):
        card_lines.append(f"کارت {idx}:")
        card_lines.append(f"شماره کارت: `{card['number']}`")
        card_lines.append(f"صاحب کارت: {card['owner']}")
        card_lines.append("")
    await q.edit_message_text(
        "💰 افزایش موجودی ریالی\n\n"
        f"{chr(10).join(card_lines).strip()}\n\n"
        f"قیمت فعلی TON: {format_money(get_setting('ton_price', '220000'))}\n\n"
        "مبلغ واریزی را به تومان بفرست.",
        parse_mode=ParseMode.MARKDOWN,
        reply_markup=kb_back(),
    )


async def cb_buy_ton(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    user = get_user(update.effective_user.id)
    ctx.user_data["state"] = "waiting_buy_ton_amount"
    await q.edit_message_text(
        "🛒 خرید TON\n\n"
        f"موجودی تومان شما: {format_money(user['rial_balance'] if user else 0)}\n"
        f"قیمت هر TON: {format_money(get_setting('ton_price', '220000'))}\n\n"
        "مقدار TON موردنظر را بفرست. مثال: `1.5`",
        parse_mode=ParseMode.MARKDOWN,
        reply_markup=kb_back(),
    )


async def cb_confirm_buy_ton(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    amount = float(ctx.user_data.get("buy_ton_amount", 0))
    rial_cost = float(ctx.user_data.get("buy_ton_rial_cost", 0))
    user = get_user(update.effective_user.id)
    if not user or float(user["rial_balance"] or 0) < rial_cost:
        await q.edit_message_text(
            "❌ موجودی تومان کافی نیست.",
            reply_markup=InlineKeyboardMarkup(
                [
                    [sbtn("💰 افزایش موجودی", callback_data="add_balance", style="success")],
                    [sbtn("🔙 بازگشت", callback_data="main_menu", style="danger")],
                ]
            ),
        )
        return
    adjust_rial_balance(user["user_id"], -rial_cost, "خرید TON")
    adjust_ton_balance(user["user_id"], amount, "خرید TON")
    ctx.user_data.pop("buy_ton_amount", None)
    ctx.user_data.pop("buy_ton_rial_cost", None)
    await q.edit_message_text(
        "✅ خرید TON انجام شد.\n\n"
        f"مبلغ کسر شده: {format_money(rial_cost)}\n"
        f"مقدار افزوده شده: {format_ton(amount)}",
        reply_markup=kb_main(update.effective_user.id),
    )


async def cb_transfer_ton(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    user = get_user(update.effective_user.id)
    if not user or not user["verified"]:
        await q.edit_message_text(
            "⚠️ برای انتقال TON ابتدا باید احراز هویتت تایید شده باشد.",
            reply_markup=InlineKeyboardMarkup(
                [
                    [sbtn("🛡 شروع احراز هویت", callback_data="add_balance", style="success")],
                    [sbtn("🔙 بازگشت", callback_data="main_menu", style="danger")],
                ]
            ),
        )
        return
    if not payout_auto_ready():
        await q.edit_message_text(
            "⚠️ انتقال خودکار هنوز آماده نیست.\n\n"
            "کلید `payout API` باید توسط ادمین تنظیم شود.",
            reply_markup=kb_back(),
        )
        return
    ctx.user_data["state"] = "waiting_withdraw_address"
    await q.edit_message_text(
        "📤 انتقال TON\n\n"
        f"موجودی فعلی: {format_ton(user['ton_balance'])}\n"
        "این انتقال بعد از ثبت، به‌صورت خودکار از سمت سرور انجام می‌شود.\n\n"
        "اول آدرس مقصد را بفرست.",
        reply_markup=kb_back(),
    )


async def cb_skip_withdraw_comment(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    ctx.user_data["withdraw_comment"] = ""
    await send_withdraw_confirm(q, ctx)


async def send_withdraw_confirm(q, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    amount = float(ctx.user_data.get("withdraw_amount", 0))
    address = ctx.user_data.get("withdraw_address", "")
    comment = ctx.user_data.get("withdraw_comment", "")
    fee_percent = parse_number(get_setting("withdrawal_fee_percent", "1")) or 1
    fee_amount = round(amount * fee_percent / 100, 9)
    net_amount = round(max(amount - fee_amount, 0), 9)
    processing_text = "ارسال خودکار از سمت سرور فعال است."
    rows = [
        [sbtn("✅ تایید واریزی", callback_data="submit_withdraw", style="success")],
        [sbtn("🔙 بازگشت", callback_data="main_menu", style="danger")],
    ]
    await q.edit_message_text(
        "📤 تایید انتقال TON\n\n"
        f"آدرس مقصد: `{address}`\n"
        f"مبلغ: {format_ton(amount)}\n"
        f"کارمزد: {format_ton(fee_amount)}\n"
        f"خالص ارسالی: {format_ton(net_amount)}\n"
        f"کامنت: {comment or 'ندارد'}\n"
        f"وضعیت: {processing_text}",
        parse_mode=ParseMode.MARKDOWN,
        reply_markup=InlineKeyboardMarkup(rows),
    )


async def cb_submit_withdraw(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    user = get_user(update.effective_user.id)
    if not user:
        await q.edit_message_text("❌ کاربر پیدا نشد.", reply_markup=kb_main(update.effective_user.id))
        return
    amount = float(ctx.user_data.get("withdraw_amount", 0))
    address = ctx.user_data.get("withdraw_address", "")
    comment = ctx.user_data.get("withdraw_comment", "")
    min_withdraw = parse_number(get_setting("min_withdraw_ton", "0.5")) or 0.5
    if amount < min_withdraw:
        await q.edit_message_text(
            f"❌ حداقل برداشت {format_ton(min_withdraw)} است.",
            reply_markup=kb_back("transfer_ton"),
        )
        return
    if float(user["ton_balance"] or 0) < amount:
        await q.edit_message_text(
            "❌ موجودی TON کافی نیست.",
            reply_markup=InlineKeyboardMarkup(
                [
                    [sbtn("🛒 خرید TON", callback_data="buy_ton", style="success")],
                    [sbtn("🔙 بازگشت", callback_data="main_menu", style="danger")],
                ]
            ),
        )
        return
    if not payout_auto_ready():
        await q.edit_message_text(
            "❌ انتقال خودکار هنوز آماده نیست. کلید payout تنظیم نشده است.",
            reply_markup=kb_main(update.effective_user.id),
        )
        return
    adjust_ton_balance(user["user_id"], -amount, "ثبت درخواست برداشت")
    withdrawal_id = create_withdrawal_request(user["user_id"], amount, address, comment, "queued")
    auto_result = try_auto_withdraw(withdrawal_id)
    if auto_result["ok"]:
        await q.edit_message_text(
            "✅ انتقال به‌صورت خودکار برای OxaPay ارسال شد.\n\n"
            f"شناسه پیگیری: `{auto_result.get('track_id', '-')}`",
            parse_mode=ParseMode.MARKDOWN,
            reply_markup=kb_main(update.effective_user.id),
        )
        return
    adjust_ton_balance(user["user_id"], amount, "برگشت برداشت ناموفق", "withdrawal", withdrawal_id)
    set_withdrawal_status(withdrawal_id, "failed", auto_result.get("message", "خطای نامشخص"))
    await q.edit_message_text(
        "❌ انتقال خودکار انجام نشد و مبلغ به موجودی TON شما برگشت.\n\n"
        f"خطا: {auto_result.get('message', 'نامشخص')}",
        reply_markup=kb_main(update.effective_user.id),
    )


async def cb_support(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    await q.answer()
    support = get_setting("support_username", "@domainjo_support")
    await q.edit_message_text(
        f"🎧 پشتیبانی: {support}",
        reply_markup=kb_back(),
    )


async def cb_admin_panel(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    await q.answer()
    await q.edit_message_text(admin_panel_text(), reply_markup=kb_admin())


async def cb_adm_verify(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    await q.answer()
    items = get_pending_verifications()
    if not items:
        await q.edit_message_text("✅ هیچ احراز هویت در انتظاری وجود ندارد.", reply_markup=kb_back("admin_panel"))
        return
    text = "✅ احراز هویت‌های در انتظار\n\n"
    buttons = []
    for item in items:
        text += f"• `{item['user_id']}` - {item['full_name'] or item['username'] or '-'}\n"
        buttons.append(
            [
                sbtn(f"✅ {item['user_id']}", callback_data=f"approve_verify_{item['user_id']}", style="success"),
                sbtn(f"❌ {item['user_id']}", callback_data=f"reject_verify_{item['user_id']}", style="danger"),
            ]
        )
    buttons.append([sbtn("🔙 بازگشت", callback_data="admin_panel", style="danger")])
    await q.edit_message_text(text, parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup(buttons))


async def cb_approve_verify(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    uid = int(q.data.split("_")[-1])
    set_verified(uid, 1)
    await q.answer("احراز هویت تایید شد", show_alert=True)
    try:
        await ctx.bot.send_message(uid, "✅ احراز هویت شما تایید شد.")
    except Exception:
        logger.exception("Failed to notify user verify approve")
    await cb_adm_verify(update, ctx)


async def cb_reject_verify(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    uid = int(q.data.split("_")[-1])
    conn = db()
    conn.execute(
        "UPDATE users SET verify_file=NULL, card_file=NULL, verified=0, updated_at=? WHERE user_id=?",
        (now_iso(), uid),
    )
    conn.commit()
    conn.close()
    await q.answer("احراز هویت رد شد", show_alert=True)
    try:
        await ctx.bot.send_message(uid, "❌ احراز هویت شما رد شد. لطفاً دوباره مدارک را ارسال کنید.")
    except Exception:
        logger.exception("Failed to notify user verify reject")
    await cb_adm_verify(update, ctx)


async def cb_adm_deposits(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    await q.answer()
    deposits = get_pending_deposits()
    if not deposits:
        await q.edit_message_text("✅ درخواست شارژ در انتظاری وجود ندارد.", reply_markup=kb_back("admin_panel"))
        return
    text = "💰 شارژهای در انتظار\n\n"
    buttons = []
    for item in deposits:
        text += f"• #{item['id']} - `{item['user_id']}` - {format_money(item['rial_amount'])}\n"
        buttons.append(
            [
                sbtn(f"✅ #{item['id']}", callback_data=f"approve_deposit_{item['id']}", style="success"),
                sbtn(f"❌ #{item['id']}", callback_data=f"reject_deposit_{item['id']}", style="danger"),
            ]
        )
    buttons.append([sbtn("🔙 بازگشت", callback_data="admin_panel", style="danger")])
    await q.edit_message_text(text, parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup(buttons))


async def cb_approve_deposit(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    deposit_id = int(q.data.split("_")[-1])
    deposit = approve_deposit(deposit_id)
    if not deposit:
        await q.answer("این درخواست قابل تایید نیست.", show_alert=True)
        return
    await q.answer("شارژ تایید شد", show_alert=True)
    try:
        await ctx.bot.send_message(
            deposit["user_id"],
            "✅ افزایش موجودی ریالی شما تایید شد.\n"
            f"مبلغ افزوده شده: {format_money(deposit['rial_amount'])}\n"
            f"می‌تونی حالا از بخش خرید TON استفاده کنی.",
        )
    except Exception:
        logger.exception("Failed to notify user deposit approve")
    await cb_adm_deposits(update, ctx)


async def cb_reject_deposit(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    deposit_id = int(q.data.split("_")[-1])
    deposit = reject_deposit(deposit_id, "رد توسط ادمین")
    if not deposit:
        await q.answer("این درخواست قابل رد نیست.", show_alert=True)
        return
    await q.answer("شارژ رد شد", show_alert=True)
    try:
        await ctx.bot.send_message(
            deposit["user_id"],
            "❌ درخواست افزایش موجودی شما رد شد. در صورت نیاز با پشتیبانی تماس بگیرید.",
        )
    except Exception:
        logger.exception("Failed to notify user deposit reject")
    await cb_adm_deposits(update, ctx)


async def cb_adm_withdrawals(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    await q.answer()
    items = get_pending_withdrawals()
    if not items:
        await q.edit_message_text("✅ برداشت در انتظاری وجود ندارد.", reply_markup=kb_back("admin_panel"))
        return
    text = "📤 برداشت‌های در انتظار\n\n"
    buttons = []
    for item in items:
        text += f"• #{item['id']} - `{item['user_id']}` - {format_ton(item['amount'])}\n"
        buttons.append(
            [
                sbtn(f"✅ #{item['id']}", callback_data=f"approve_withdraw_{item['id']}", style="success"),
                sbtn(f"❌ #{item['id']}", callback_data=f"reject_withdraw_{item['id']}", style="danger"),
            ]
        )
    buttons.append([sbtn("🔙 بازگشت", callback_data="admin_panel", style="danger")])
    await q.edit_message_text(text, parse_mode=ParseMode.MARKDOWN, reply_markup=InlineKeyboardMarkup(buttons))


async def cb_approve_withdraw(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    withdrawal_id = int(q.data.split("_")[-1])
    withdrawal = get_withdrawal(withdrawal_id)
    if not withdrawal or withdrawal["status"] not in {"pending", "queued"}:
        await q.answer("این برداشت قابل تایید نیست.", show_alert=True)
        return
    result = try_auto_withdraw(withdrawal_id)
    if result["ok"]:
        await q.answer("برداشت به OxaPay ارسال شد", show_alert=True)
        try:
            await ctx.bot.send_message(
                withdrawal["user_id"],
                "✅ درخواست انتقال شما تایید شد و در حال پردازش است.\n"
                f"شناسه پیگیری: {result.get('track_id', '-')}",
            )
        except Exception:
            logger.exception("Failed to notify user withdrawal approve")
    else:
        set_withdrawal_status(withdrawal_id, "approved_manual", result["message"])
        await q.answer("کلید یا API آماده نبود؛ برداشت به حالت تاییدشده دستی رفت.", show_alert=True)
        try:
            await ctx.bot.send_message(
                withdrawal["user_id"],
                "✅ درخواست انتقال شما تایید شد.\n"
                "ادمین باید پرداخت را دستی نهایی کند.",
            )
        except Exception:
            logger.exception("Failed to notify user manual withdrawal approve")
    await cb_adm_withdrawals(update, ctx)


async def cb_reject_withdraw(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    withdrawal_id = int(q.data.split("_")[-1])
    withdrawal = refund_withdrawal(withdrawal_id, "رد توسط ادمین")
    if not withdrawal:
        await q.answer("این برداشت قابل رد نیست.", show_alert=True)
        return
    await q.answer("برداشت رد شد و موجودی برگشت.", show_alert=True)
    try:
        await ctx.bot.send_message(
            withdrawal["user_id"],
            "❌ درخواست انتقال شما رد شد و مبلغ به موجودی TON شما برگشت.",
        )
    except Exception:
        logger.exception("Failed to notify user withdrawal reject")
    await cb_adm_withdrawals(update, ctx)


async def cb_adm_users(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    await q.answer()
    users = get_all_users()
    text = "👥 آخرین کاربران\n\n"
    for item in users:
        badge = "✅" if item["verified"] else "❌"
        text += (
            f"{badge} `{item['user_id']}` - {item['full_name'] or item['username'] or '-'}\n"
            f"{format_money(item['rial_balance'])} | {format_ton(item['ton_balance'])}\n\n"
        )
    await q.edit_message_text(text[:4090], parse_mode=ParseMode.MARKDOWN, reply_markup=kb_back("admin_panel"))


async def cb_adm_charge(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    await q.answer()
    ctx.user_data["state"] = "adm_charge_uid"
    await q.edit_message_text(
        "💸 شارژ دستی کاربر\n\nآیدی عددی کاربر را بفرست.",
        reply_markup=kb_back("admin_panel"),
    )


async def cb_adm_cards(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    await q.answer()
    cards = get_card_slots()
    text = "💳 مدیریت کارت‌ها\n\n"
    buttons = []
    for idx, card in enumerate(cards, start=1):
        number = card["number"] or "تنظیم نشده"
        owner = card["owner"] or "تنظیم نشده"
        text += f"کارت {idx}: {number} | {owner}\n"
        buttons.append([sbtn(f"✏️ ویرایش کارت {idx}", callback_data=f"adm_card_slot_{idx}", style="primary")])
    buttons.append([sbtn("🔙 بازگشت", callback_data="admin_panel", style="danger")])
    await q.edit_message_text(text, reply_markup=InlineKeyboardMarkup(buttons))


async def cb_adm_card_slot(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    q = update.callback_query
    if not is_admin(update.effective_user.id):
        await q.answer("دسترسی ندارید", show_alert=True)
        return
    slot = int(q.data.rsplit("_", 1)[-1])
    ctx.user_data["adm_card_slot"] = slot
    ctx.user_data["state"] = "adm_card_number"
    await q.answer()
    await q.edit_message_text(
        f"💳 تنظیم کارت {slot}\n\nشماره کارت را بفرست.",
        reply_markup=kb_back("adm_cards"),
    )


async def cmd_admin(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    user = update.effective_user
    if is_admin(user.id):
        await update.message.reply_text(admin_panel_text(), reply_markup=kb_admin())
        return
    ctx.user_data["state"] = "admin_login"
    await update.message.reply_text("رمز عبور ادمین را وارد کن.")


async def cmd_setprice(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    if not ctx.args:
        await update.message.reply_text("مثال: /setprice 220000")
        return
    price = parse_number(" ".join(ctx.args))
    if not price or price <= 0:
        await update.message.reply_text("قیمت نامعتبر است.")
        return
    set_setting("ton_price", int(price))
    await update.message.reply_text(f"✅ قیمت TON روی {format_money(price)} تنظیم شد.")


async def cmd_setcard(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    args = ctx.args
    if not args:
        await update.message.reply_text("مثال: /setcard 1 6037990000000000")
        return
    slot = 1
    if args[0].isdigit() and 1 <= int(args[0]) <= 3:
        slot = int(args[0])
        args = args[1:]
    value = " ".join(args).strip()
    if not value:
        await update.message.reply_text("مثال: /setcard 1 6037990000000000")
        return
    set_card_slot(slot - 1, number=value)
    await update.message.reply_text(f"✅ شماره کارت {slot} ذخیره شد.")


async def cmd_setowner(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    args = ctx.args
    if not args:
        await update.message.reply_text("مثال: /setowner 1 علی رضایی")
        return
    slot = 1
    if args[0].isdigit() and 1 <= int(args[0]) <= 3:
        slot = int(args[0])
        args = args[1:]
    value = " ".join(args).strip()
    if not value:
        await update.message.reply_text("مثال: /setowner 1 علی رضایی")
        return
    set_card_slot(slot - 1, owner=value)
    await update.message.reply_text(f"✅ صاحب کارت {slot} ذخیره شد.")


async def cmd_setwallet(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    value = " ".join(ctx.args).strip()
    if not value:
        await update.message.reply_text("مثال: /setwallet UQ...")
        return
    set_setting("merchant_ton_wallet", value)
    await update.message.reply_text("✅ آدرس ولت اصلی ذخیره شد.")


async def cmd_setoxapay(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    value = " ".join(ctx.args).strip()
    if "|" in value:
        general_key, payout_key = [part.strip() for part in value.split("|", 1)]
        set_setting("oxapay_general_api_key", general_key)
        set_setting("oxapay_payout_api_key", payout_key)
        await update.message.reply_text("✅ کلیدهای عمومی و payout ذخیره شدند.")
        return
    if not value:
        await update.message.reply_text("مثال: /setoxapay GENERAL|PAYOUT")
        return
    set_setting("oxapay_general_api_key", value)
    set_setting("oxapay_payout_api_key", value)
    await update.message.reply_text("✅ همان کلید برای هر دو بخش عمومی و payout ذخیره شد.")


async def cmd_auto_withdraw(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    set_setting("withdrawal_auto", "1")
    if payout_auto_ready():
        await update.message.reply_text("✅ برداشت خودکار فعال است و انتقال‌ها بدون تایید ادمین مستقیماً ارسال می‌شوند.")
    else:
        await update.message.reply_text("⚠️ منطق برداشت روی حالت خودکار است، اما هنوز کلید payout تنظیم نشده.")


async def cmd_pending(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    items = get_pending_verifications()
    if not items:
        await update.message.reply_text("هیچ احراز هویت در انتظاری وجود ندارد.")
        return
    text = "احراز هویت‌های در انتظار:\n\n"
    for item in items:
        text += f"`{item['user_id']}` - {item['full_name'] or item['username'] or '-'}\n"
    await update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN)


async def cmd_pending_deposits(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    items = get_pending_deposits()
    if not items:
        await update.message.reply_text("هیچ شارژی در انتظار نیست.")
        return
    text = "شارژهای در انتظار:\n\n"
    for item in items:
        text += f"#{item['id']} - `{item['user_id']}` - {format_money(item['rial_amount'])}\n"
    await update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN)


async def cmd_pending_withdrawals(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    items = get_pending_withdrawals()
    if not items:
        await update.message.reply_text("هیچ برداشتی در انتظار نیست.")
        return
    text = "برداشت‌های در انتظار:\n\n"
    for item in items:
        text += f"#{item['id']} - `{item['user_id']}` - {format_ton(item['amount'])}\n"
    await update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN)


async def cmd_stats(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    await update.message.reply_text(admin_panel_text())


async def cmd_user(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    if not ctx.args or not ctx.args[0].isdigit():
        await update.message.reply_text("مثال: /user 123456")
        return
    uid = int(ctx.args[0])
    user = get_user(uid)
    if not user:
        await update.message.reply_text("کاربر پیدا نشد.")
        return
    await update.message.reply_text(
        "اطلاعات کاربر\n\n"
        f"آیدی: {uid}\n"
        f"نام: {user['full_name'] or '-'}\n"
        f"یوزرنیم: @{user['username'] or '-'}\n"
        f"احراز: {'بله' if user['verified'] else 'خیر'}\n"
        f"تومان: {format_money(user['rial_balance'])}\n"
        f"TON: {format_ton(user['ton_balance'])}\n"
        f"ولت: {user['ton_wallet'] or 'ثبت نشده'}"
    )


async def cmd_addton(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    if len(ctx.args) < 2 or not ctx.args[0].isdigit():
        await update.message.reply_text("مثال: /addton 123456 1.25")
        return
    uid = int(ctx.args[0])
    amount = parse_number(ctx.args[1])
    if amount is None:
        await update.message.reply_text("مقدار نامعتبر است.")
        return
    adjust_ton_balance(uid, amount, "شارژ دستی ادمین")
    await update.message.reply_text(f"✅ {format_ton(amount)} به کاربر {uid} اضافه شد.")
    try:
        await ctx.bot.send_message(uid, f"💎 {format_ton(amount)} توسط ادمین به حساب شما اضافه شد.")
    except Exception:
        logger.exception("Failed to notify addton")


async def cmd_addrial(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    if len(ctx.args) < 2 or not ctx.args[0].isdigit():
        await update.message.reply_text("مثال: /addrial 123456 500000")
        return
    uid = int(ctx.args[0])
    amount = parse_number(ctx.args[1])
    if amount is None:
        await update.message.reply_text("مقدار نامعتبر است.")
        return
    adjust_rial_balance(uid, amount, "شارژ دستی ادمین")
    await update.message.reply_text(f"✅ {format_money(amount)} به کاربر {uid} اضافه شد.")
    try:
        await ctx.bot.send_message(uid, f"💰 {format_money(amount)} توسط ادمین به حساب شما اضافه شد.")
    except Exception:
        logger.exception("Failed to notify addrial")


async def cmd_broadcast(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    if not is_admin(update.effective_user.id):
        return
    message = " ".join(ctx.args).strip()
    if not message:
        await update.message.reply_text("مثال: /broadcast سلام")
        return
    users = get_all_users(limit=100000)
    sent = 0
    for user in users:
        try:
            await ctx.bot.send_message(user["user_id"], message)
            sent += 1
        except Exception:
            logger.exception("Failed to broadcast to %s", user["user_id"])
    await update.message.reply_text(f"✅ پیام برای {sent} کاربر ارسال شد.")


async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
    user_obj = update.effective_user
    upsert_user(user_obj.id, user_obj.username, user_obj.full_name)
    state = ctx.user_data.get("state")
    text = (update.message.text or "").strip()
    photo = update.message.photo

    try:
        await update.message.set_reaction([ReactionTypeEmoji("👍")])
    except Exception:
        pass

    if state == "admin_login":
        if text == get_setting("admin_password", ADMIN_DEFAULT_PASSWORD):
            if add_admin(user_obj.id, user_obj.username or str(user_obj.id)):
                ctx.user_data["state"] = None
                await update.message.reply_text("✅ به عنوان ادمین وارد شدی.", reply_markup=kb_main(user_obj.id))
            else:
                await update.message.reply_text("❌ ظرفیت ادمین پر است.")
        else:
            await update.message.reply_text("❌ رمز عبور اشتباه است.")
        return

    if state == "waiting_wallet_address":
        if not validate_ton_address(text):
            await update.message.reply_text("⚠️ آدرس ولت معتبر نیست. دوباره بفرست.")
            return
        set_user_wallet(user_obj.id, text)
        ctx.user_data["state"] = None
        await update.message.reply_text("✅ ولت شما ذخیره شد.", reply_markup=kb_main(user_obj.id))
        return

    if state == "waiting_verify_selfie":
        if not photo:
            await update.message.reply_text("⚠️ لطفاً عکس سلفی احراز هویت را ارسال کن.")
            return
        conn = db()
        conn.execute(
            "UPDATE users SET verify_file=?, updated_at=? WHERE user_id=?",
            (photo[-1].file_id, now_iso(), user_obj.id),
        )
        conn.commit()
        conn.close()
        ctx.user_data["state"] = "waiting_verify_card"
        await update.message.reply_text("✅ دریافت شد. حالا عکس کارت بانکی را بفرست.")
        return

    if state == "waiting_verify_card":
        if not photo:
            await update.message.reply_text("⚠️ لطفاً عکس کارت بانکی را ارسال کن.")
            return
        card_photo = photo[-1].file_id
        conn = db()
        conn.execute(
            "UPDATE users SET card_file=?, updated_at=? WHERE user_id=?",
            (card_photo, now_iso(), user_obj.id),
        )
        conn.commit()
        row = conn.execute("SELECT verify_file FROM users WHERE user_id=?", (user_obj.id,)).fetchone()
        conn.close()
        ctx.user_data["state"] = None
        await update.message.reply_text(
            "✅ مدارک ارسال شد و منتظر تایید ادمین هستی.",
            reply_markup=kb_main(user_obj.id),
        )
        if row and row["verify_file"]:
            await notify_admins_about_verification(ctx, user_obj.id, user_obj.full_name, row["verify_file"], card_photo)
        return

    if state == "waiting_rial_topup_amount":
        amount = parse_number(text)
        if amount is None or amount <= 0:
            await update.message.reply_text("⚠️ مبلغ معتبر بفرست.")
            return
        ton_price = parse_number(get_setting("ton_price", "220000")) or 220000
        ton_estimate = amount / ton_price if ton_price > 0 else 0
        ctx.user_data["state"] = "waiting_rial_receipt"
        ctx.user_data["topup_amount"] = amount
        await update.message.reply_text(
            "📸 فیش یا رسید پرداخت را ارسال کن.\n\n"
            f"مبلغ: {format_money(amount)}\n"
            f"معادل تقریبی TON با قیمت فعلی: {format_ton(ton_estimate)}"
        )
        return

    if state == "waiting_rial_receipt":
        if not photo:
            await update.message.reply_text("⚠️ لطفاً رسید را به‌صورت عکس ارسال کن.")
            return
        user = get_user(user_obj.id)
        wallet = user["ton_wallet"] if user else ""
        deposit_id = create_deposit_request(
            user_obj.id,
            float(ctx.user_data.get("topup_amount", 0)),
            wallet or "",
            photo[-1].file_id,
        )
        ctx.user_data["state"] = None
        ctx.user_data.pop("topup_amount", None)
        await update.message.reply_text(
            f"✅ درخواست افزایش موجودی ثبت شد. شماره درخواست: #{deposit_id}",
            reply_markup=kb_main(user_obj.id),
        )
        await notify_admins_about_deposit(ctx, deposit_id)
        return

    if state == "waiting_buy_ton_amount":
        amount = parse_number(text)
        if amount is None or amount <= 0:
            await update.message.reply_text("⚠️ مقدار TON معتبر بفرست.")
            return
        ton_price = parse_number(get_setting("ton_price", "220000")) or 220000
        rial_cost = amount * ton_price
        ctx.user_data["state"] = None
        ctx.user_data["buy_ton_amount"] = round(amount, 9)
        ctx.user_data["buy_ton_rial_cost"] = round(rial_cost, 2)
        await update.message.reply_text(
            "🛒 تایید خرید TON\n\n"
            f"مقدار: {format_ton(amount)}\n"
            f"هزینه: {format_money(rial_cost)}",
            reply_markup=InlineKeyboardMarkup(
                [
                    [sbtn("✅ تایید خرید", callback_data="confirm_buy_ton", style="success")],
                    [sbtn("🔙 بازگشت", callback_data="main_menu", style="danger")],
                ]
            ),
        )
        return

    if state == "waiting_withdraw_address":
        if not validate_ton_address(text):
            await update.message.reply_text("⚠️ آدرس TON معتبر نیست. دوباره بفرست.")
            return
        ctx.user_data["withdraw_address"] = text
        ctx.user_data["state"] = "waiting_withdraw_amount"
        await update.message.reply_text("حالا مقدار TON را بفرست. مثال: 1.25")
        return

    if state == "waiting_withdraw_amount":
        amount = parse_number(text)
        if amount is None or amount <= 0:
            await update.message.reply_text("⚠️ مقدار معتبر بفرست.")
            return
        ctx.user_data["withdraw_amount"] = round(amount, 9)
        ctx.user_data["state"] = "waiting_withdraw_comment"
        await update.message.reply_text(
            "اگر کامنت داری بفرست؛ اگر نمی‌خواهی، دکمه رد شدن کامنت را بزن.",
            reply_markup=InlineKeyboardMarkup(
                [
                    [sbtn("⏭ رد شدن کامنت", callback_data="withdraw_comment_skip", style="primary")],
                    [sbtn("🔙 منوی اصلی", callback_data="main_menu", style="danger")],
                ]
            ),
        )
        return

    if state == "waiting_withdraw_comment":
        ctx.user_data["state"] = None
        ctx.user_data["withdraw_comment"] = text
        await update.message.reply_text(
            "✅ کامنت ذخیره شد.",
            reply_markup=InlineKeyboardMarkup(
                [
                    [sbtn("ادامه تایید", callback_data="withdraw_comment_skip", style="success")],
                ]
            ),
        )
        return

    if state == "adm_charge_uid":
        if not text.isdigit():
            await update.message.reply_text("⚠️ آیدی عددی بفرست.")
            return
        ctx.user_data["adm_charge_uid"] = int(text)
        ctx.user_data["state"] = "adm_charge_amount"
        await update.message.reply_text("مبلغ تومان را بفرست.")
        return

    if state == "adm_charge_amount":
        amount = parse_number(text)
        uid = ctx.user_data.get("adm_charge_uid")
        if amount is None:
            await update.message.reply_text("⚠️ عدد معتبر بفرست.")
            return
        adjust_rial_balance(uid, amount, "شارژ دستی از پنل")
        ctx.user_data["state"] = None
        ctx.user_data.pop("adm_charge_uid", None)
        await update.message.reply_text("✅ شارژ انجام شد.", reply_markup=kb_admin())
        try:
            await ctx.bot.send_message(uid, f"💰 {format_money(amount)} به حساب شما اضافه شد.")
        except Exception:
            logger.exception("Failed to notify manual charge")
        return

    if state == "adm_card_number":
        slot = ctx.user_data.get("adm_card_slot")
        if slot not in {1, 2, 3}:
            ctx.user_data["state"] = None
            await update.message.reply_text("⚠️ اسلات کارت پیدا نشد.", reply_markup=kb_admin())
            return
        number = re.sub(r"\s+", "", text)
        if not re.fullmatch(r"\d{16,24}", number):
            await update.message.reply_text("⚠️ شماره کارت معتبر بفرست.")
            return
        set_card_slot(slot - 1, number=number)
        ctx.user_data["state"] = "adm_card_owner"
        await update.message.reply_text(f"صاحب کارت {slot} را بفرست.")
        return

    if state == "adm_card_owner":
        slot = ctx.user_data.get("adm_card_slot")
        if slot not in {1, 2, 3}:
            ctx.user_data["state"] = None
            await update.message.reply_text("⚠️ اسلات کارت پیدا نشد.", reply_markup=kb_admin())
            return
        owner = text.strip()
        if not owner:
            await update.message.reply_text("⚠️ نام صاحب کارت را بفرست.")
            return
        set_card_slot(slot - 1, owner=owner)
        ctx.user_data["state"] = None
        ctx.user_data.pop("adm_card_slot", None)
        await update.message.reply_text(f"✅ کارت {slot} کامل ذخیره شد.", reply_markup=kb_admin())
        return

    await update.message.reply_text("از منوی زیر انتخاب کن:", reply_markup=kb_main(user_obj.id))


def main() -> None:
    init_db()
    telegram_request = HTTPXRequest(
        connection_pool_size=8,
        connect_timeout=20.0,
        read_timeout=30.0,
        write_timeout=30.0,
        pool_timeout=5.0,
        proxy=None,
        httpx_kwargs={"trust_env": False},
    )
    app = (
        Application.builder()
        .token(BOT_TOKEN)
        .request(telegram_request)
        .get_updates_request(telegram_request)
        .build()
    )

    app.add_handler(CommandHandler("start", cmd_start))
    app.add_handler(CommandHandler("admin", cmd_admin))
    app.add_handler(CommandHandler("setprice", cmd_setprice))
    app.add_handler(CommandHandler("setcard", cmd_setcard))
    app.add_handler(CommandHandler("setowner", cmd_setowner))
    app.add_handler(CommandHandler("setwallet", cmd_setwallet))
    app.add_handler(CommandHandler("setoxapay", cmd_setoxapay))
    app.add_handler(CommandHandler("auto_withdraw", cmd_auto_withdraw))
    app.add_handler(CommandHandler("pending", cmd_pending))
    app.add_handler(CommandHandler("pending_deposits", cmd_pending_deposits))
    app.add_handler(CommandHandler("pending_withdrawals", cmd_pending_withdrawals))
    app.add_handler(CommandHandler("stats", cmd_stats))
    app.add_handler(CommandHandler("user", cmd_user))
    app.add_handler(CommandHandler("addton", cmd_addton))
    app.add_handler(CommandHandler("addrial", cmd_addrial))
    app.add_handler(CommandHandler("broadcast", cmd_broadcast))

    app.add_handler(CallbackQueryHandler(cb_main_menu, pattern="^main_menu$"))
    app.add_handler(CallbackQueryHandler(cb_my_account, pattern="^my_account$"))
    app.add_handler(CallbackQueryHandler(cb_set_wallet, pattern="^set_wallet$"))
    app.add_handler(CallbackQueryHandler(cb_add_balance, pattern="^add_balance$"))
    app.add_handler(CallbackQueryHandler(cb_buy_ton, pattern="^buy_ton$"))
    app.add_handler(CallbackQueryHandler(cb_confirm_buy_ton, pattern="^confirm_buy_ton$"))
    app.add_handler(CallbackQueryHandler(cb_transfer_ton, pattern="^transfer_ton$"))
    app.add_handler(CallbackQueryHandler(cb_skip_withdraw_comment, pattern="^withdraw_comment_skip$"))
    app.add_handler(CallbackQueryHandler(cb_submit_withdraw, pattern="^submit_withdraw$"))
    app.add_handler(CallbackQueryHandler(cb_support, pattern="^support$"))

    app.add_handler(CallbackQueryHandler(cb_admin_panel, pattern="^admin_panel$"))
    app.add_handler(CallbackQueryHandler(cb_adm_verify, pattern="^adm_verify$"))
    app.add_handler(CallbackQueryHandler(cb_approve_verify, pattern="^approve_verify_"))
    app.add_handler(CallbackQueryHandler(cb_reject_verify, pattern="^reject_verify_"))
    app.add_handler(CallbackQueryHandler(cb_adm_deposits, pattern="^adm_deposits$"))
    app.add_handler(CallbackQueryHandler(cb_approve_deposit, pattern="^approve_deposit_"))
    app.add_handler(CallbackQueryHandler(cb_reject_deposit, pattern="^reject_deposit_"))
    app.add_handler(CallbackQueryHandler(cb_adm_withdrawals, pattern="^adm_withdrawals$"))
    app.add_handler(CallbackQueryHandler(cb_approve_withdraw, pattern="^approve_withdraw_"))
    app.add_handler(CallbackQueryHandler(cb_reject_withdraw, pattern="^reject_withdraw_"))
    app.add_handler(CallbackQueryHandler(cb_adm_users, pattern="^adm_users$"))
    app.add_handler(CallbackQueryHandler(cb_adm_charge, pattern="^adm_charge$"))
    app.add_handler(CallbackQueryHandler(cb_adm_cards, pattern="^adm_cards$"))
    app.add_handler(CallbackQueryHandler(cb_adm_card_slot, pattern="^adm_card_slot_"))

    app.add_handler(MessageHandler(filters.ALL & ~filters.COMMAND, handle_message))

    logger.info("TON wallet bot started")
    try:
        app.run_polling(drop_pending_updates=True)
    except NetworkError as exc:
        logger.error(
            "Telegram connection failed. Check internet/VPN/firewall. Details: %s",
            exc,
        )
        print(
            "\n[Telegram Network Error]\n"
            "اتصال به سرورهای تلگرام برقرار نشد.\n"
            "این مشکل از شبکه/فایروال/VPN سیستم است، نه منطق ربات.\n"
            "لطفاً اینترنت یا VPN را بررسی کن و دوباره اجرا بزن.\n"
            f"جزئیات: {exc}\n"
        )
        raise


if __name__ == "__main__":
    main()
