"""
Handler: /start, main menu, profile, help, market rates, and order history.
All user-facing text in Persian (فارسی).
"""

import contextlib
import logging
from aiogram import Router, F
from aiogram.fsm.context import FSMContext
from aiogram.types import (
    Message,
    CallbackQuery,
    ReplyKeyboardMarkup,
    KeyboardButton,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
)
from aiogram.filters import CommandStart, Command
from aiogram.exceptions import TelegramBadRequest

from database.db import get_or_create_user, get_user_orders, get_total_users, update_user_phone, user_has_phone, get_wallet_balance
from states.states import VirtualNumberStates, WalletStates, TicketStates
from keyboards.inline import (
    main_menu_kb,
    back_to_menu_kb,
    market_rates_refresh_kb,
    welcome_inline_kb,
)
from keyboards.reply import main_reply_kb, virtual_services_reply_kb
from keyboards.admin_reply import admin_reply_kb
from keyboards.callback_data import WelcomeCallback
from filters import IsAdmin, IsDynamicService
from utils.emojis import get_pe, get_plain_emoji
from utils.kalino import get_panel_balance

router = Router(name="user")

logger = logging.getLogger(__name__)


# ─── Registration / Rules ───────────────────────────────────────────

RULES_TEXT = (
    f"{get_pe('pushpin')} <b>نکات لازم در هنگام خرید شماره مجازی:</b>\n\n"
    "1️⃣ شماره های ستاره دار داخل لیست نوریپ و نوبن هستند و شماره های دارای لایک از کددهی سریعتری برخوردارند.\n"
    "2️⃣ برای تلگرام از فیلترشکن رایگان استفاده نکنید و حتما از نسخه اصلی تلگرام استفاده کنید.\n"
    "3️⃣ برای واتساپ سعی کنید به ip کشوری وصل شوید که قصد استفاده از شماره مجازی آن کشور را دارید !\n"
    "4️⃣ حتما حتما لازم است که برنامه مورد نظر ، کد تایید ارسال کند . ممکن است متوجه نشوید که برای تایید شماره به شما تماس گرفته باشد ، پس حتما گزینه ارسال پیامک تایید را انتخاب کنید.\n"
    "5️⃣ هر شماره تا 15 دقیقه قابلیت استفاده دارد و در این مدت تا هر چند تا کد که لازم دارید را می‌توانید دریافت کنید.\n\n"
    f"{get_pe('pointing_down')} جهت موافقت با قوانین و ورود به ربات، روی دکمه <b>«تایید قوانین و ارسال شماره»</b> کلیک کنید:"
)


def contact_request_kb() -> ReplyKeyboardMarkup:
    """Reply keyboard with a single request_contact button for onboarding."""
    return ReplyKeyboardMarkup(
        keyboard=[[KeyboardButton(text=f"{get_plain_emoji('mobile')} تایید قوانین و ارسال شماره", request_contact=True)]],
        resize_keyboard=True,
        one_time_keyboard=True,
    )


# ─── /start ──────────────────────────────────────────────────────────

@router.message(CommandStart())
async def cmd_start(message: Message) -> None:
    """Register the user and show the Ultimate Welcome Experience.

    If the user has NOT shared their phone number yet, show the rules
    and a request_contact button instead of the main menu.
    """
    await get_or_create_user(
        user_id=message.from_user.id,
        username=message.from_user.username,
        full_name=message.from_user.full_name,
    )

    # ── Mandatory registration: check for phone number ──
    has_phone = await user_has_phone(message.from_user.id)
    if not has_phone:
        # New user or user who hasn't shared contact yet — show rules
        await message.answer(
            RULES_TEXT,
            reply_markup=contact_request_kb(),
        )
        return

    is_admin = await IsAdmin()(message)

    text = (
        f"{get_pe('wave')} <b>سلام {message.from_user.first_name} عزیز!</b>\n\n"

        "━━━━━━━━━━━━━━━━━━━━━━━━\n"
        f"{get_pe('bot')} <b>فروشگاه هوشمند تلگرام</b>\n"
        "━━━━━━━━━━━━━━━━━━━━━━━━\n\n"

        f"{get_pe('sparkles')} <b>چرا ما؟</b>\n"
        f"{get_pe('check')} تحویل <b>خودکار و آنی</b> پس از پرداخت\n"
        f"{get_pe('call')} پشتیبانی <b>۲۴ ساعته</b> در ۷ روز هفته\n"
        f"{get_pe('card')} پرداخت <b>امن</b> از طریق زرین‌پال\n"
        f"{get_pe('money')} قیمت‌های <b>رقابتی</b> با نرخ لحظه‌ای ارز\n"
        f"{get_pe('lock')} <b>گارانتی</b> کیفیت تمامی خدمات\n\n"

        f"{get_pe('mobile')} ارائه دهنده خدمات <b>شماره مجازی</b> و خرید انواع <b>اکانت‌های پرمیوم</b>\n\n"

        "━━━━━━━━━━━━━━━━━━━━━━━━\n"
        f"{get_pe('box')} <b>خدمات ما:</b>\n"
        f"{get_pe('key_lock')} شماره مجازی\n"
        f"{get_pe('bot')} خرید انواع اکانت‌های پرمیوم\n"
        f"{get_pe('star')} تلگرام پرمیوم (ماهانه تا سالانه)\n"
        f"{get_pe('heart_simple')} گیفت و استارز تلگرام\n"
        f"{get_pe('fire')} خدمات طراحی حرفه‌ای\n"
        f"{get_pe('shield')} امنیت صفحه و اکانت\n"
        "━━━━━━━━━━━━━━━━━━━━━━━━"
    )

    if is_admin:
        text += (
            f"\n{get_pe('medal')} <b>شما به عنوان مدیر شناخته شدید.</b>\n"
            "برای ورود به پنل مدیریت دستور /admin را ارسال کنید."
        )
        # Admins get the same premium inline shop menu.
        # Admin panel remains available through /admin.
        from aiogram.types import ReplyKeyboardRemove

        await message.answer(
            text,
            reply_markup=await main_menu_kb(),
        )
        await message.answer(
            f"{get_pe('gear')} <b>پنل مدیریت</b>\nبرای ورود به پنل مدیریت دستور /admin را ارسال کنید."
        )
    else:
        # Regular users: premium inline main menu
        from aiogram.types import ReplyKeyboardRemove

        await message.answer(
            text,
            reply_markup=await main_menu_kb(),
        )


# ─── Contact sharing (mandatory registration) ───────────────────────

@router.message(F.contact)
async def process_contact(message: Message) -> None:
    """Handle shared contact — verify it belongs to the sender, save, and welcome."""
    if message.contact.user_id != message.from_user.id:
        await message.answer(
            f"{get_pe('warning')} لطفاً شماره تماس <b>خودتان</b> را با استفاده از دکمه زیر ارسال کنید.",
            reply_markup=contact_request_kb(),
        )
        return

    phone_number = message.contact.phone_number
    try:
        await update_user_phone(message.from_user.id, phone_number)
    except Exception as exc:
        logger.error(
            "Failed to save phone for user %s: %s", message.from_user.id, exc,
        )

    from aiogram.types import ReplyKeyboardRemove

    await message.answer(
        f"{get_pe('check')} ثبت نام شما با موفقیت انجام شد و قوانین پذیرفته شد.\n"
        f"به فروشگاه خوش آمدید!",
        reply_markup=ReplyKeyboardRemove(),
    )
    await message.answer(
        "✦ منوی اصلی فروشگاه:",
        reply_markup=await main_menu_kb(),
    )


# ─── WelcomeCallback handlers (inline buttons under welcome text) ────

@router.callback_query(WelcomeCallback.filter(F.action == "categories"))
async def cb_welcome_categories(callback: CallbackQuery) -> None:
    """Show the main shop menu with all product categories."""
    await callback.answer()
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('home')} <b>دسته‌بندی خدمات</b>\nیک سرویس را انتخاب کنید:",
            reply_markup=await main_menu_kb(),
        )
    await callback.answer()


@router.callback_query(WelcomeCallback.filter(F.action == "deals"))
async def cb_welcome_deals(callback: CallbackQuery) -> None:
    """Show special offers / discounts."""
    await callback.answer()
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('fire')} <b>تخفیف‌های ویژه</b>\n\n"
            f"{get_pe('diamond')} <b>پکیج پرمیوم تلگرام + استارز</b>\n"
            "   خرید اشتراک سالانه + ۵۰۰ استارز با ۱۵٪ تخفیف\n\n"
            f"{get_pe('bot')} <b>اکانت هوش مصنوعی</b>\n"
            "   خرید همزمان ChatGPT + Gemini با ۱۰٪ تخفیف\n\n"
            f"{get_pe('star')} برای بهره‌مندی از تخفیف‌ها، محصول مورد نظر را "
            "از منوی خرید انتخاب کنید.\n\n"
            f"{get_pe('calendar')} تخفیف‌ها محدود هستند!",
            reply_markup=back_to_menu_kb(),
        )
    await callback.answer()


# ─── Reply keyboard: 🛍 فروشگاه ────────────────────────────────────

@router.message(F.text.contains("فروشگاه"))
async def reply_btn_shop(message: Message) -> None:
    """Show the inline shop menu when the user taps the shop button."""
    await message.answer(
        f"{get_pe('home')} <b>منوی خرید</b>\nیک سرویس را انتخاب کنید:",
        reply_markup=await main_menu_kb(),
    )


# ─── Reply keyboard: 👤 پروفایل ────────────────────────────────────

@router.message(F.text.contains("پروفایل"))
async def reply_btn_profile(message: Message) -> None:
    """Show the user's profile information."""
    user = await get_or_create_user(
        user_id=message.from_user.id,
        username=message.from_user.username,
        full_name=message.from_user.full_name,
    )

    username_display = f"@{user['username']}" if user["username"] else "—"
    admin_badg = f" | {get_pe('shield')} مدیر" if user["is_admin"] else ""
    wallet_balance = await get_wallet_balance(message.from_user.id)

    text = (
        f"{get_pe('user')} <b>پروفایل من</b>\n\n"
        f"{get_pe('id_icon')} شناسه: <code>{user['user_id']}</code>\n"
        f"{get_pe('name_badge')} نام: {user['full_name']}\n"
        f"{get_pe('user')} یوزرنیم: {username_display}{admin_badg}\n"
        f"{get_pe('purse')} موجودی کیف پول: <b>{wallet_balance:,}</b> تومان\n"
        f"{get_pe('calendar')} تاریخ عضویت: {user['joined_at'][:10] if user['joined_at'] else '—'}"
    )
    await message.answer(text, reply_markup=main_reply_kb())


# ─── Reply keyboard: 💰 افزایش موجودی ──────────────────────────────

@router.message(F.text.contains("افزایش موجودی"))
async def reply_btn_increase_balance(message: Message, state: FSMContext) -> None:
    """Show wallet recharge options."""
    from states.states import WalletStates
    await state.set_state(WalletStates.enter_amount)
    await message.answer(
        f"{get_pe('purse')} <b>افزایش موجودی کیف پول</b>\n\n"
        "لطفاً مبلغ مورد نظر را به <b>تومان</b> وارد کنید:\n"
        "(حداقل: ۱۰,۰۰۰ تومان)\n\n"
        "مثال: <code>50000</code>",
        reply_markup=back_to_menu_kb(),
    )


@router.message(WalletStates.enter_amount)
async def msg_wallet_amount(message: Message, state: FSMContext) -> None:
    """Receive wallet recharge amount and initiate payment."""
    from states.states import WalletStates
    from utils.pricing import price_display_raw
    from utils.zarinpal import request_payment
    from database.db import create_order, create_payment, update_payment_authority
    from keyboards.inline import pay_link_kb

    text = message.text.strip().replace(",", "").replace("،", "")
    try:
        amount = int(text)
        if amount < 10000:
            raise ValueError
    except ValueError:
        await message.answer(
            f"{get_pe('warning')} لطفاً عددی بزرگتر از ۱۰,۰۰۰ ارسال کنید.",
            reply_markup=back_to_menu_kb(),
        )
        return

    # Create order for wallet recharge
    order_id = await create_order(
        user_id=message.from_user.id,
        product="شارژ کیف پول",
        details=f"مبلغ شارژ: {amount:,} تومان",
        amount_irt=amount,
    )

    result = await request_payment(
        amount_irt=amount,
        description=f"شارژ کیف پول — {amount:,} تومان",
    )

    if not result.success or not result.authority:
        await message.answer(
            f"{get_pe('cross')} درخواست پرداخت ناموفق بود. لطفاً بعداً دوباره تلاش کنید.",
            reply_markup=back_to_menu_kb(),
        )
        await state.clear()
        return

    payment_id = await create_payment(order_id, amount)
    await update_payment_authority(payment_id, result.authority)
    await state.update_data(order_id=order_id, payment_id=payment_id, authority=result.authority, amount_irt=amount)
    await state.set_state(WalletStates.payment)

    amount_str = f"{amount:,}".replace(",", "،")
    await message.answer(
        f"{get_pe('purse')} <b>شارژ کیف پول</b>\n\n"
        f"{get_pe('money')} مبلغ: <b>{amount_str} تومان</b>\n\n"
        "برای پرداخت روی دکمه زیر کلیک کنید:\n"
        "<i>پس از پرداخت موفق، موجودی کیف پول شما افزایش می‌یابد.</i>",
        reply_markup=pay_link_kb(result.start_pay_url),
    )


# ─── Reply keyboard: 🎧 پشتیبانی ───────────────────────────────────

@router.message(F.text.contains("پشتیبانی"))
async def reply_btn_support(message: Message, state: FSMContext) -> None:
    """Redirect to the ticket system and set FSM state so the next message is captured."""
    from states.states import TicketStates
    await state.set_state(TicketStates.waiting_message)
    await message.answer(
        f"{get_pe('call')} <b>پشتیبانی</b>\n\n"
        f"{get_pe('ticket')} لطفاً پیام یا مشکل خود را به صورت متنی بنویسید.\n"
        "پیام شما مستقیماً به تیم پشتیبانی ارسال خواهد شد.\n\n"
        f"برای انصراف، روی دکمه «{get_pe('back_arrow')} بازگشت به منو» کلیک کنید.",
        reply_markup=back_to_menu_kb(),
    )


# ─── Reply keyboard: 📋 سفارشات ─────────────────────────────────

@router.message(F.text.contains("سفارشات"))
async def reply_btn_orders(message: Message) -> None:
    """Show the user's order history when the orders button is tapped."""
    orders = await get_user_orders(message.from_user.id)
    if not orders:
        await message.answer(
            f"{get_pe('ticket')} <b>پیگیری سفارشات</b>\n\nشما هنوز سفارشی ثبت نکرده‌اید.",
            reply_markup=main_reply_kb(),
        )
    else:
        lines = [f"{get_pe('ticket')} <b>پیگیری سفارشات</b>\n"]
        for o in orders[:10]:
            lines.append(_format_order_line(o))
        lines.append("\nبرای اطلاعات بیشتر روی شماره سفارش کلیک کنید.")
        await message.answer(
            "\n".join(lines),
            reply_markup=main_reply_kb(),
        )


# ─── Back to menu ────────────────────────────────────────────────────

@router.callback_query(F.data == "menu:back")
async def cb_back_to_menu(callback: CallbackQuery) -> None:
    await callback.answer()
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('home')} <b>منوی اصلی</b>\nیک سرویس را انتخاب کنید:",
            reply_markup=await main_menu_kb(),
        )
    await callback.answer()


# ─── Menu entry points ──────────────────────────────────────────────

@router.callback_query(F.data == "menu:premium")
async def cb_menu_premium(callback: CallbackQuery) -> None:
    await callback.answer()
    from keyboards.inline import premium_duration_kb
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('star')} <b>تلگرام پرمیوم</b>\nیک پلن اشتراک را انتخاب کنید:",
            reply_markup=await premium_duration_kb(),
        )
    await callback.answer()


@router.callback_query(F.data == "menu:ai_accounts")
async def cb_menu_ai(callback: CallbackQuery) -> None:
    await callback.answer()
    from keyboards.inline import ai_platform_kb
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('bot')} <b>اکانت هوش مصنوعی پرمیوم</b>\nیک پلتفرم را انتخاب کنید:",
            reply_markup=await ai_platform_kb(),
        )
    await callback.answer()


@router.callback_query(F.data == "menu:design")
async def cb_menu_design(callback: CallbackQuery) -> None:
    await callback.answer()
    from keyboards.inline import design_category_kb
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('fire')} <b>خدمات طراحی</b>\nیک دسته را انتخاب کنید:",
            reply_markup=await design_category_kb(),
        )
    await callback.answer()


@router.callback_query(F.data == "menu:security")
async def cb_menu_security(callback: CallbackQuery) -> None:
    await callback.answer()
    from keyboards.inline import security_tariff_kb
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('shield')} <b>امنیت صفحه</b>\nیک تعرفه را انتخاب کنید:",
            reply_markup=await security_tariff_kb(),
        )
    await callback.answer()


# ─── Virtual Number (شماره مجازی) — Kalino API ─────────────────────

@router.callback_query(F.data == "menu:virtual_number")
async def cb_menu_virtual_number(callback: CallbackQuery) -> None:
    """Show ShizNumber services for virtual-number purchases."""
    try:
        from utils.shiznumber import get_services
        from keyboards.inline import virtual_services_kb

        await callback.answer()

        services = await get_services()

        # فقط 36 سرویس اول ShizNumber
        if isinstance(services, list):
            services = services[:36]

        if not isinstance(services, list) or not services:
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('warning')} خطا در دریافت شماره‌ها. لطفاً دوباره تلاش کنید.\n"
                    "لطفاً کمی بعد تلاش کنید.",
                    reply_markup=back_to_menu_kb(),
                )
            return

        services_map = {}

        for item in services:
            if not isinstance(item, dict):
                continue

            slug = item.get("slug") or item.get("service_slug")
            if not slug:
                continue

            label = (
                item.get("fa_name")
                or item.get("name")
                or item.get("title")
                or str(slug)
            )

            label = str(label).strip()
            slug = str(slug).strip()

            if label:
                services_map[label] = slug

        if not services_map:
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('warning')} هیچ سرویس فعالی پیدا نشد.",
                    reply_markup=back_to_menu_kb(),
                )
            return

        text = (
            f"{get_pe('chart_up')} جهت خرید شماره مجازی، "
            "سرویس موردنظر را انتخاب کنید:"
        )

        await callback.message.edit_text(
            text,
            reply_markup=await virtual_services_kb(
                services_map=services_map,
                page=0,
            ),
        )

    except Exception as e:
        logger.error(
            f"CRASH IN MENU VIRTUAL SHIZNUMBER: {e}",
            exc_info=True,
        )

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('warning')} خطایی در دریافت سرویس‌های شماره مجازی رخ داد.",
                reply_markup=back_to_menu_kb(),
            )


@router.message(IsDynamicService())
async def process_service_selection(
    message: Message,
    state: FSMContext,
    service_id,
) -> None:
    """Handle a service tapped from the dynamic ReplyKeyboard.

    The ``IsDynamicService`` filter resolves the tapped text against the
    live Kalino services map and passes the matching ``service_id``.
    """
    from utils.kalino import get_telegram_countries, get_apps_display_map
    from keyboards.inline import virtual_country_kb

    services_map = await get_apps_display_map()

    await message.answer(f"{get_pe('hourglass')} در حال دریافت لیست کشورها...")

    countries = await get_telegram_countries(service_id)

    if not countries:
        await message.answer(
            f"{get_pe('warning')} در حال حاضر شماره‌ای برای این سرویس موجود نیست.\n"
            "لطفاً بعداً دوباره تلاش کنید.",
            reply_markup=virtual_services_reply_kb(services_map),
        )
        return

    await state.set_state(VirtualNumberStates.choose_country)
    await state.update_data(
        virtual_service_id=str(service_id),
        virtual_countries=countries,
        bulk_qty=1,
    )

    text = _virtual_service_text(service_id, message.text)

    await message.answer(text, reply_markup=virtual_country_kb(countries, service_id, 0))


def _virtual_service_text(service_id, service_name: str) -> str:
    """Build the country-selection header, tailored per app."""
    sid_str = str(service_id)

    if sid_str == "1":
        srv_name = "تلگرام"
    elif sid_str == "tg_noreport":
        srv_name = "تلگرام غیرریپورت"
    else:
        srv_name = service_name or "سرویس"

    if sid_str in ("1", "tg_noreport"):
        return (
            f"{get_pe('diamond')} <b>سرویس {srv_name} انتخاب شد</b>\n"
            f"{get_pe('pointing')} جهت خرید شماره مجازی روی نام کشور مورد نظر خود کلیک نمایید:\n\n"
            f"{get_pe('warning')} تمامی شماره‌ها برای راحتی شما از قبل بر روی تلگرام ثبت نام شده‌اند، "
            f"کشورهایی که با {get_pe('rocket')} مشخص شده‌اند خام می‌باشند!"
        )
    return (
        f"{get_pe('web')} <b>سرویس {srv_name} انتخاب شد.</b>\n"
        f"{get_pe('pointing')} جهت خرید شماره مجازی روی نام کشور مورد نظر خود کلیک نمایید:"
    )


@router.message(F.text == f"{get_plain_emoji('back_arrow')} بازگشت")
async def reply_btn_virtual_back(message: Message, state: FSMContext) -> None:
    """Handle the Back button from the virtual-services reply keyboard.

    Returns to the main reply keyboard.
    """
    await state.clear()
    await message.answer(
        f"{get_pe('home')} <b>منوی اصلی</b>",
        reply_markup=main_reply_kb(),
    )


@router.callback_query(F.data.startswith("v_app:"))
async def cb_app_selected(
    callback: CallbackQuery,
    state: FSMContext,
) -> None:
    """Load ShizNumber inventories for the selected service."""
    try:
        service_slug = callback.data.split(":", 1)[1].strip()

        from utils.shiznumber import get_service_numbers
        from keyboards.inline import virtual_country_kb

        await callback.answer()
        await callback.message.edit_text(
            f"{get_pe('hourglass')} در حال دریافت لیست شماره‌ها..."
        )

        data = await get_service_numbers(service_slug)

        if not isinstance(data, list):
            data = []

        countries = []

        # Existing pricing configuration: 30% account margin,
        # applied exactly once.
        from database.db import get_profit_margin
        from config import config

        margin_percent = await get_profit_margin(
            config.ACCOUNT_PROFIT_MARGIN_PERCENT
        )
        margin = float(margin_percent or 0) / 100.0

        for item in data:
            if not isinstance(item, dict):
                continue

            try:
                number_id = int(item.get("id"))
            except (TypeError, ValueError):
                continue

            country_data = item.get("country")

            if isinstance(country_data, dict):
                fa_name = str(
                    country_data.get("fa_name")
                    or country_data.get("name")
                    or ""
                ).strip()
            else:
                fa_name = str(
                    item.get("fa_name")
                    or item.get("name")
                    or ""
                ).strip()

            # Telegram: only Shiz1 inventories.
            # Other services use the available ShizNumber inventory directly.
            if service_slug.lower() == "telegram" and "shiz1" not in fa_name.lower():
                continue

            raw_price = item.get(
                "price",
                item.get("amount", 0)
            )

            try:
                base_price = int(float(raw_price))
            except (TypeError, ValueError):
                continue

            raw_count = item.get("count", 0)

            try:
                count = int(
                    float(
                        str(raw_count)
                        .replace(",", "")
                        .replace("،", "")
                    )
                )
            except (TypeError, ValueError):
                count = 0

            country_data = item.get("country")

            if isinstance(country_data, dict):
                country_name = (
                    country_data.get("fa_name")
                    or country_data.get("name")
                    or country_data.get("en_name")
                    or fa_name
                    or "نامشخص"
                )
            else:
                country_name = (
                    country_data
                    or item.get("country_name")
                    or fa_name
                    or "نامشخص"
                )

            country_name = str(country_name).strip()

            final_price = int(
                base_price + (base_price * margin)
            )

            countries.append({
                "range": str(number_id),
                "number_id": number_id,
                "country": country_name,
                "name": fa_name,
                "base_price": base_price,
                "final_price": final_price,
                "count": count,
                "in_stock": count > 0,
                "provider": "shiznumber",
                "country_slug": (
                    country_data.get("slug")
                    if isinstance(country_data, dict)
                    else item.get("country_slug")
                ),
                "service_slug": service_slug,
            })

        # نمایش شماره‌ها از ارزان‌ترین قیمت نهایی تا گران‌ترین
        countries.sort(
            key=lambda x: (
                x["final_price"],
                x["number_id"],
            )
        )

        if not countries:
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('warning')} "
                    "در حال حاضر شماره Shiz1 برای این سرویس موجود نیست.",
                    reply_markup=back_to_menu_kb(),
                )
            return

        await state.set_state(VirtualNumberStates.choose_country)

        await state.update_data(
            virtual_service_id="shiznumber",
            shiz_service_slug=service_slug,
            virtual_countries=countries,
            bulk_qty=1,
        )

        text = _virtual_service_text(
            service_slug,
            "شماره مجازی",
        )

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                text,
                reply_markup=virtual_country_kb(
                    countries,
                    "shiznumber",
                    page=0,
                ),
            )

    except Exception as e:
        logger.error(
            f"CRASH IN SHIZNUMBER APP HANDLER: {e}",
            exc_info=True,
        )

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('warning')} "
                "خطایی در دریافت شماره‌ها رخ داد.",
                reply_markup=back_to_menu_kb(),
            )

        await callback.answer()


@router.callback_query(F.data == "menu:back_main")
async def cb_back_to_main(callback: CallbackQuery) -> None:
    """Back to the main shop menu (used by the Virtual Number grid footer)."""
    await callback.answer()
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            f"{get_pe('home')} <b>منوی اصلی</b>\nیک سرویس را انتخاب کنید:",
            reply_markup=await main_menu_kb(),
        )
    await callback.answer()


@router.callback_query(F.data.startswith("vs_page:"))
async def cb_services_page(callback: CallbackQuery) -> None:
    """Paginate ShizNumber virtual-number services only."""
    try:
        page = max(0, int(callback.data.split(":", 1)[1]))

        from utils.shiznumber import get_services
        from keyboards.inline import virtual_services_kb

        services = await get_services()

        # فقط 36 سرویس اول ShizNumber
        if isinstance(services, list):
            services = services[:36]

        if not isinstance(services, list):
            services = []

        services_map = {}

        for item in services:
            if not isinstance(item, dict):
                continue

            slug = item.get("slug") or item.get("service_slug")
            if not slug:
                continue

            label = (
                item.get("fa_name")
                or item.get("name")
                or item.get("title")
                or str(slug)
            )

            label = str(label).strip()
            slug = str(slug).strip()

            if label and slug:
                services_map[label] = slug

        if not services_map:
            await callback.answer("سرویس شماره مجازی در دسترس نیست.", show_alert=True)
            return

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_reply_markup(
                reply_markup=await virtual_services_kb(
                    services_map=services_map,
                    page=page,
                )
            )

        await callback.answer()

    except Exception as e:
        logger.error(f"CRASH IN SHIZNUMBER SERVICES PAGINATION: {e}", exc_info=True)
        await callback.answer()


@router.callback_query(F.data.startswith("v_page:"))
async def cb_virtual_country_page(callback: CallbackQuery, state: FSMContext) -> None:
    """Paginate numbers without mixing ShizNumber with Kalino."""
    try:
        parts = callback.data.split(":")
        if len(parts) < 3:
            await callback.answer("داده نامعتبر است.", show_alert=True)
            return

        service_id = parts[1]
        page = max(0, int(parts[2]))

        from keyboards.inline import virtual_country_kb, virtual_services_kb

        data = await state.get_data()
        countries = data.get("virtual_countries")
        cached_service_id = str(data.get("virtual_service_id", ""))

        if str(service_id) == "shiznumber":
            if not countries or cached_service_id != "shiznumber":
                from utils.shiznumber import get_telegram_numbers_with_prices

                countries = await get_telegram_numbers_with_prices()
                await state.update_data(
                    virtual_service_id="shiznumber",
                    virtual_countries=countries,
                )
        else:
            if not countries or cached_service_id != str(service_id):
                from utils.kalino import get_telegram_countries

                countries = await get_telegram_countries(service_id)
                await state.update_data(
                    virtual_service_id=service_id,
                    virtual_countries=countries,
                )

        if not countries:
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('warning')} لیست شمارهها منقضی شده است.",
                    reply_markup=await virtual_services_kb(),
                )
            await callback.answer()
            return

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_reply_markup(
                reply_markup=virtual_country_kb(
                    countries,
                    service_id,
                    page=page,
                )
            )

        await callback.answer()

    except Exception as e:
        logger.error(f"CRASH IN PAGINATION: {e}", exc_info=True)
        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('warning')} خطایی رخ داد. لطفاً دوباره تلاش کنید.",
                reply_markup=await virtual_services_kb(),
            )
        await callback.answer()


@router.callback_query(F.data.startswith("v_buy:"))
async def cb_virtual_buy(callback: CallbackQuery, state: FSMContext) -> None:
    """User tapped a country row — show confirmation with the final price.

    The callback data format is ``v_buy:{service_id}:{country_range}``
    where country_range is the Kalino short range code.
    """
    try:
        parts = callback.data.split(":")
        if len(parts) < 3:
            await callback.answer(f"{get_pe('warning')} داده نامعتبر.", show_alert=True)
            return

        service_id = parts[1]
        country = ":".join(parts[2:])  # range code; may contain ':'

        from utils.kalino import get_telegram_countries
        from keyboards.inline import virtual_services_kb, back_to_menu_kb

        data = await state.get_data()
        countries = data.get("virtual_countries", [])
        if not countries or data.get("virtual_service_id") != service_id:
            countries = await get_telegram_countries(service_id)
            if countries:
                await state.update_data(
                    virtual_service_id=service_id,
                    virtual_countries=countries,
                )

        if not countries:
            await callback.answer(f"{get_pe('warning')} لیست کشورها منقضی شده است. لطفاً دوباره انتخاب کنید.", show_alert=True)
            return

        selected = next((c for c in countries if str(c.get("range")) == country), None)

        if selected is None:
            await callback.answer(f"{get_pe('warning')} کشور یافت نشد.", show_alert=True)
            return

        if not selected.get("in_stock"):
            await callback.answer(f"{get_pe('warning')} این کشور در حال حاضر موجود نیست.", show_alert=True)
            return

        qty = int(data.get("bulk_qty") or 1)
        unit_price = selected.get("final_price", selected.get("base_price", 0))
        final_price = int(unit_price) * qty
        base_price_total = int(selected.get("base_price", 0)) * qty
        price_str = f"{final_price:,}".replace(",", "،")
        country_name = selected.get("country", "نامشخص")

        wallet_balance = await get_wallet_balance(callback.from_user.id)
        shortage = max(0, final_price - wallet_balance)
        shortage_str = f"{shortage:,}".replace(",", "،")

        await state.update_data(
            virtual_service_id=service_id,
            country_name=country_name,
            country_key=country,
            qty=qty,
            price_toman=final_price,
            base_price_toman=base_price_total,
        )
        await state.set_state(VirtualNumberStates.confirm_buy)

        from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
        from utils.emojis import get_plain_emoji

        if shortage <= 0:
            confirm_label = f"{get_plain_emoji('green_circle')} خرید از کیف پول"
        else:
            confirm_label = f"{get_plain_emoji('green_circle')} پرداخت — {shortage_str} تومان"

        confirm_kb = InlineKeyboardMarkup(inline_keyboard=[
            [InlineKeyboardButton(text=confirm_label,
                                  callback_data="virtual:confirm")],
            [InlineKeyboardButton(text=f"{get_plain_emoji('red_circle')} انصراف",
                                  callback_data=f"v_page:{service_id}:0")],
        ])

        qty_line = f"{get_pe('box')} تعداد: <b>{qty}</b> عدد\n" if qty > 1 else ""
        wallet_line = f"{get_pe('purse')} موجودی کیف پول شما: <b>{wallet_balance:,}</b> تومان\n"
        if shortage > 0:
            pay_line = f"{get_pe('card')} مبلغ پرداختی از درگاه: <b>{shortage_str} تومان</b>\n"
        else:
            pay_line = f"{get_pe('check')} کل مبلغ از <b>کیف پول</b> کسر می‌شود.\n"

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('key_lock')} <b>خرید شماره مجازی</b>\n\n"
                f"{get_pe('web')} کشور: <b>{country_name}</b>\n"
                f"{qty_line}"
                f"{get_pe('money')} قیمت نهایی: <b>{price_str} تومان</b>\n"
                f"{wallet_line}{pay_line}\n"
                "پس از خرید، یک شماره مجازی دریافت خواهید کرد.\n"
                "کد تأیید ظرف چند دقیقه برای شما ارسال می‌شود.\n\n"
                "آیا مطمئن هستید؟",
                reply_markup=confirm_kb,
            )
        await callback.answer()
    except Exception as e:
        logger.error(f"CRASH IN V_BUY: {e}", exc_info=True)
        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('warning')} خطایی رخ داد. لطفاً دوباره تلاش کنید.",
                reply_markup=await virtual_services_kb(),
            )
        await callback.answer()


@router.callback_query(F.data == "virtual:confirm", VirtualNumberStates.confirm_buy)
async def cb_virtual_confirm_rules(callback: CallbackQuery, state: FSMContext) -> None:
    """Show virtual-number purchase rules before any payment/order action."""
    rules = """<b>⚠️ قوانین خرید شماره مجازی</b>

1️⃣ شماره های شیز۱(shiz1) را روی نسخه های غیر رسمی تلگرام مثل تلگرام پلاس و ایکس و ... وارد کنید تا کد را دریافت کنید.اگر تلگرام اصلی باشه کد میاد اما قابل استفاده نیست و بابت کد دادن تلگرام ازتون هزینه میگیره اونجوری و ... و ما کاری دیگ براتون از دستمون برنمیاد و...

2️⃣ برای شماره های تلگرام آنهایی که بغلشون ستاره دار هست شماره های غیرریپورت هستند.

3️⃣ برای تلگرام از فیلترشکن رایگان استفاده نکنید و حتما از نسخه اصلی تلگرام استفاده کنید.

4️⃣ برای واتساپ سعی کنید به ip کشوری وصل شوید که قصد استفاده از شماره مجازی آن کشور را دارید !

5️⃣ حتما حتما لازم است که برنامه مورد نظر ، کد تایید ارسال کند . ممکن است متوجه نشوید که برای تایید شماره به شماره تماس گرفته باشد ، پس حتما گزینه ارسال پیامک تایید را انتخاب کنید.

6️⃣ هر شماره تا 15 دقیقه قابلیت استفاده دارد و در این مدت تا هر چند تا کد که لازم دارید را می‌توانید دریافت کنید.

7️⃣ مبالغ به حساب شما، درصورت خطای شما و کد آمدن به شماره و شارژ حساب برگردانده نمی‌شود.

<b>با ادامه خرید، قوانین بالا را تأیید می‌کنید.</b>"""

    from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton

    rules_kb = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(
            text="✅ تأیید قوانین و ادامه پرداخت",
            callback_data="virtual:rules_accept"
        )],
        [InlineKeyboardButton(
            text="❌ انصراف",
            callback_data="virtual:rules_cancel"
        )],
    ])

    await callback.answer()
    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            rules,
            reply_markup=rules_kb,
        )


@router.callback_query(F.data == "virtual:rules_accept", VirtualNumberStates.confirm_buy)
async def cb_virtual_confirm_buy(callback: CallbackQuery, state: FSMContext) -> None:
    """User confirmed — pay the DIFFERENCE from the wallet + gateway.

    Wallet math:
        shortage = total_price - user_wallet_balance
      • shortage > 0  → Zarinpal invoice is STRICTLY for ``shortage``.
      • shortage <= 0 → Zarinpal is bypassed; the wallet pays and delivery
                        runs immediately.
    """
    wallet_used = 0
    order_id = None
    try:
        await callback.answer()
        data = await state.get_data()
        service_id = data.get("virtual_service_id", "")
        country_key = data.get("country_key")
        country_name = data.get("country_name", "نامشخص")
        qty = int(data.get("qty") or 1)
        price_toman = data.get("price_toman", 0)

        if not service_id or not country_key or not price_toman:
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('warning')} اطلاعات سفارش ناقص است. لطفاً دوباره شروع کنید.",
                    reply_markup=await main_menu_kb(),
                )
            await state.clear()
            return

        from database.db import (
            create_order,
            create_payment,
            update_payment_authority,
            update_order_status,
            get_wallet_balance,
            deduct_from_wallet,
            add_to_wallet,
        )
        from utils.zarinpal import request_payment
        from utils.delivery import deliver_product
        from keyboards.inline import pay_link_kb

        final_irt = int(price_toman)
        base_price = data.get("base_price_toman", final_irt)

        # ── Pre-purchase panel balance check (Kalino only) ──
        # ShizNumber has its own provider wallet, so do not query Kalino balance.
        panel_balance = None
        if str(service_id) != "shiznumber":
            panel_balance = await get_panel_balance()

        if panel_balance is not None and panel_balance < base_price:
            with contextlib.suppress(Exception):
                await callback.message.edit_text(
                    f"{get_pe('warning')} <b>موجودی پنل کافی نیست.</b>\n\n"
                    "لطفاً دقایقی بعد دوباره تلاش کنید.",
                    reply_markup=back_to_menu_kb(),
                )
            await state.clear()

            # Calculate accounting details
            api_price = data.get("base_price_toman", base_price)  # Original API price (total)
            bot_price = final_irt  # Our price with margin (total)
            profit = bot_price - api_price  # Our profit
            shortage_amount = base_price - panel_balance  # How much short we are

            # Silently alert admins with full accounting details
            from config import config as _cfg
            for admin_id in _cfg.ADMIN_IDS:
                with contextlib.suppress(Exception):
                    username_display = f"@{callback.from_user.username}" if callback.from_user.username else "ندارد"
                    await callback.message.bot.send_message(
                        admin_id,
                        f"{get_pe('siren')} <b>هشدار فوری: موجودی پنل تمام شده</b>\n\n"
                        f"{get_pe('earth')} سرویس: <b>{service_id}</b> | کشور: <code>{country_key}</code> | تعداد: <b>{qty}</b>\n"
                        f"{get_pe('user')} کاربر: <code>{callback.from_user.id}</code> ({username_display})\n\n"
                        f"{get_pe('money')} قیمت اصلی API: <b>{api_price:,}</b> تومان\n"
                        f"{get_pe('tag')} قیمت ربات (با سود): <b>{bot_price:,}</b> تومان\n"
                        f"{get_pe('chart_up')} سود ما: <b>{profit:,}</b> تومان\n"
                        f"{get_pe('box')} موجودی فعلی پنل: <b>{panel_balance:,}</b> تومان\n"
                        f"{get_pe('warning')} کسری: <b>{shortage_amount:,}</b> تومان"
                    )
            return

        user_wallet_balance = await get_wallet_balance(callback.from_user.id)
        shortage = final_irt - user_wallet_balance
        order_details = f"سرویس: {service_id} | کشور: {country_key} | تعداد: {qty}"

        if shortage <= 0:
            # ── Wallet covers everything — bypass Zarinpal entirely ──
            if not await deduct_from_wallet(callback.from_user.id, final_irt):
                with contextlib.suppress(TelegramBadRequest):
                    await callback.message.edit_text(
                        f"{get_pe('cross')} موجودی کیف پول شما کافی نیست.\n"
                        "لطفاً کیف پول خود را شارژ کنید یا دوباره تلاش کنید.",
                        reply_markup=back_to_menu_kb(),
                    )
                await state.clear()
                return

            order_id = await create_order(
                user_id=callback.from_user.id,
                product=f"شماره مجازی: {country_name}",
                details=order_details,
                amount_irt=final_irt,
            )
            await update_order_status(order_id, "paid")

            # Confirm the wallet charge to the user, then deliver instantly.
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('purse')} <b>پرداخت از کیف پول انجام شد!</b>\n\n"
                    f"{get_pe('web')} کشور: <b>{country_name}</b>\n"
                    f"{get_pe('money')} مبلغ کسر شده: <b>{final_irt:,}</b> تومان\n\n"
                    "در حال تحویل شماره مجازی…",
                    reply_markup=None,
                )
            await deliver_product(
                bot=callback.bot,
                user_id=callback.from_user.id,
                order_id=order_id,
                product=f"شماره مجازی: {country_name}",
                details=order_details,
            )
            await state.clear()
            return

        # ── Partial wallet: the gateway charge is ONLY the shortage ──
        order_id = await create_order(
            user_id=callback.from_user.id,
            product=f"شماره مجازی: {country_name}",
            details=order_details,
            amount_irt=final_irt,
        )

        result = await request_payment(
            amount_irt=shortage,
            description=f"خرید {qty if qty > 1 else ''}شماره مجازی {country_name}",
        )

        if not result.success or not result.authority:
            await update_order_status(order_id, "cancelled")
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('cross')} درخواست پرداخت ناموفق بود:\n"
                    "لطفاً بعداً دوباره تلاش کنید.",
                    reply_markup=back_to_menu_kb(),
                )
            await state.clear()
            return

        wallet_used = min(user_wallet_balance, final_irt)  # == wallet here (shortage>0)
        if wallet_used > 0 and not await deduct_from_wallet(callback.from_user.id, wallet_used):
            await update_order_status(order_id, "cancelled")
            with contextlib.suppress(TelegramBadRequest):
                await callback.message.edit_text(
                    f"{get_pe('cross')} موجودی کیف پول شما تغییر کرده است.\n"
                    "لطفاً دوباره تلاش کنید.",
                    reply_markup=back_to_menu_kb(),
                )
            await state.clear()
            return

        payment_id = await create_payment(order_id, shortage, wallet_used)
        await update_payment_authority(payment_id, result.authority)
        await state.update_data(
            order_id=order_id, payment_id=payment_id,
            authority=result.authority, amount_irt=shortage,
            wallet_used=wallet_used,
        )
        await state.set_state(VirtualNumberStates.waiting_code)

        shortage_str = f"{shortage:,}".replace(",", "،")
        price_str = f"{final_irt:,}".replace(",", "،")
        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('card')} <b>پرداخت: شماره مجازی {country_name}</b>\n\n"
                f"{get_pe('money')} مبلغ نهایی: <b>{price_str} تومان</b>\n"
                f"{get_pe('purse')} موجودی کیف پول شما: {user_wallet_balance:,} تومان\n"
                f"{get_pe('card')} مبلغ پرداختی از درگاه: <b>{shortage_str} تومان</b>\n\n"
                "برای پرداخت روی دکمه زیر کلیک کنید:\n"
                "<i>پس از پرداخت موفق، شماره مجازی و کد تأیید برای شما ارسال خواهد شد.</i>",
                reply_markup=pay_link_kb(result.start_pay_url),
            )
        await callback.answer()
    except Exception as e:
        logger.error(f"CRASH IN VIRTUAL CONFIRM: {e}", exc_info=True)
        # Refund the wallet portion so a failed setup never eats credit.
        if wallet_used > 0:
            with contextlib.suppress(Exception):
                await add_to_wallet(callback.from_user.id, wallet_used)
        if order_id:
            with contextlib.suppress(Exception):
                await update_order_status(order_id, "cancelled")
        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('warning')} خطایی در فرآیند پرداخت رخ داد. لطفاً دوباره تلاش کنید.",
                reply_markup=back_to_menu_kb(),
            )
        await state.clear()
        await callback.answer()


@router.callback_query(F.data == "virtual:rules_cancel", VirtualNumberStates.confirm_buy)
async def cb_virtual_rules_cancel(callback: CallbackQuery, state: FSMContext) -> None:
    data = await state.get_data()
    service_id = data.get("virtual_service_id", "")
    await state.clear()
    await callback.answer()

    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            "خرید شماره مجازی لغو شد.",
            reply_markup=await virtual_services_kb(),
        )


@router.callback_query(F.data == "ignore")
async def cb_ignore(callback: CallbackQuery) -> None:
    """Silently acknowledge taps on header/disabled buttons."""
    await callback.answer()


# ─── Advanced Filter / Search (جستجوی پیشرفته) ─────────────────────

@router.callback_query(F.data.startswith("v_filter:"))
async def cb_virtual_filter(callback: CallbackQuery, state: FSMContext) -> None:
    """Start the advanced country search — ask the user to type a country name."""
    try:
        service_id = callback.data.split(":", 1)[1]

        from utils.kalino import get_telegram_countries

        data = await state.get_data()
        countries = data.get("virtual_countries")
        if not countries or data.get("virtual_service_id") != service_id:
            countries = await get_telegram_countries(service_id)
            await state.update_data(
                virtual_service_id=service_id,
                virtual_countries=countries,
            )

        if not countries:
            await callback.answer(f"{get_pe('warning')} لیست کشورها در دسترس نیست.", show_alert=True)
            return

        await state.set_state(VirtualNumberStates.country_search)
        await state.update_data(search_service_id=service_id)

        await callback.answer()
        await callback.message.answer(
            f"{get_pe('blue_circle')} <b>جستجوی پیشرفته کشور</b>\n\n"
            f"{get_pe('note')} نام کشور مورد نظر خود را بنویسید (مثلاً «آلمان» یا «ترکیه»):\n\n"
            "برای خروج، «انصراف» را ارسال کنید.",
        )
    except Exception as e:
        logger.error(f"CRASH IN V_FILTER: {e}", exc_info=True)
        await callback.answer(f"{get_pe('warning')} خطا در جستجو.", show_alert=True)


@router.message(VirtualNumberStates.country_search)
async def msg_country_search(message: Message, state: FSMContext) -> None:
    """Search the cached country list by name and render the matches."""
    text = (message.text or "").strip()

    if not text:
        return

    from keyboards.inline import virtual_country_kb, virtual_services_kb

    if text in ("انصراف", "لغو", "بازگشت"):
        await state.set_state(VirtualNumberStates.choose_country)
        data = await state.get_data()
        service_id = data.get("search_service_id") or data.get("virtual_service_id", "1")
        countries = data.get("virtual_countries") or []
        with contextlib.suppress(Exception):
            await message.answer(
                f"{get_pe('back_arrow')} جستجو لغو شد.",
                reply_markup=virtual_country_kb(countries, service_id, 0),
            )
        return

    data = await state.get_data()
    service_id = data.get("search_service_id")
    countries = data.get("virtual_countries") or []

    matches = [c for c in countries if text in str(c.get("country", ""))]

    if not matches:
        await message.answer(
            f"{get_pe('warning')} کشوری با نام «" + text[:50] + "» پیدا نشد.\n"
            "نام دیگری را امتحان کنید یا «انصراف» را ارسال کنید."
        )
        return

    await state.set_state(VirtualNumberStates.choose_country)
    await state.update_data(
        virtual_countries=matches,
        virtual_service_id=service_id,
    )

    found_qty = len(matches)
    await message.answer(
        f"{get_pe('search')} <b>{found_qty} مورد پیدا شد</b> برای «{text[:50]}»:",
        reply_markup=virtual_country_kb(matches, service_id, 0),
    )


# ─── Bulk Purchase (خرید گروهی) ─────────────────────────────────────

@router.callback_query(F.data.startswith("v_bulk:"))
async def cb_virtual_bulk(callback: CallbackQuery, state: FSMContext) -> None:
    """Open the quantity picker for bulk purchase."""
    try:
        service_id = callback.data.split(":", 1)[1]

        from utils.kalino import get_telegram_countries
        from keyboards.inline import virtual_bulk_qty_kb

        data = await state.get_data()
        countries = data.get("virtual_countries")
        if not countries or data.get("virtual_service_id") != service_id:
            countries = await get_telegram_countries(service_id)
            await state.update_data(
                virtual_service_id=service_id,
                virtual_countries=countries,
            )

        await state.set_state(VirtualNumberStates.bulk_quantity)
        await state.update_data(bulk_service_id=service_id)

        await callback.answer()
        await callback.message.answer(
            f"{get_pe('shopping')} <b>خرید گروهی شماره مجازی</b>\n\n"
            "چند شماره می‌خواهید بخرید؟",
            reply_markup=virtual_bulk_qty_kb(service_id),
        )
    except Exception as e:
        logger.error(f"CRASH IN V_BULK: {e}", exc_info=True)
        await callback.answer(f"{get_pe('warning')} خطا در خرید گروهی.", show_alert=True)


@router.callback_query(F.data.startswith("v_bulk_qty:"))
async def cb_virtual_bulk_qty(callback: CallbackQuery, state: FSMContext) -> None:
    """Quantity chosen — remember it and return to the country table."""
    try:
        parts = callback.data.split(":")
        service_id = parts[1]
        qty = int(parts[2])

        from utils.kalino import get_telegram_countries
        from keyboards.inline import virtual_country_kb

        data = await state.get_data()
        countries = data.get("virtual_countries")
        if not countries or data.get("virtual_service_id") != service_id:
            countries = await get_telegram_countries(service_id)
            await state.update_data(
                virtual_service_id=service_id,
                virtual_countries=countries,
            )

        await state.set_state(VirtualNumberStates.choose_country)
        await state.update_data(bulk_qty=qty)

        await callback.answer(f"تعداد انتخابی: {qty} شماره", show_alert=True)

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('shopping')} <b>خرید گروهی فعال</b>\nتعداد: <b>{qty}</b> شماره\n\n"
                f"{get_pe('pointing')} روی نام کشور مورد نظر خود کلیک کنید:",
                reply_markup=virtual_country_kb(countries, service_id, 0),
            )
    except Exception as e:
        logger.error(f"CRASH IN V_BULK_QTY: {e}", exc_info=True)
        await callback.answer(f"{get_pe('warning')} خطا در تعیین تعداد.", show_alert=True)


@router.callback_query(F.data.startswith("v_bulk_cancel:"))
async def cb_virtual_bulk_cancel(callback: CallbackQuery, state: FSMContext) -> None:
    """Cancel bulk purchase — reset quantity to 1."""
    try:
        service_id = callback.data.split(":", 1)[1]

        from utils.kalino import get_telegram_countries
        from keyboards.inline import virtual_country_kb

        data = await state.get_data()
        countries = data.get("virtual_countries")
        if not countries or data.get("virtual_service_id") != service_id:
            countries = await get_telegram_countries(service_id)
            await state.update_data(
                virtual_service_id=service_id,
                virtual_countries=countries,
            )

        await state.set_state(VirtualNumberStates.choose_country)
        await state.update_data(bulk_qty=1)

        await callback.answer("خرید گروهی لغو شد.", show_alert=True)

        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('web')} <b>سرویس انتخاب شد.</b>\n"
                f"{get_pe('pointing')} جهت خرید شماره مجازی روی نام کشور مورد نظر خود کلیک نمایید:",
                reply_markup=virtual_country_kb(countries, service_id, 0),
            )
    except Exception as e:
        logger.error(f"CRASH IN V_BULK_CANCEL: {e}", exc_info=True)
        await callback.answer(f"{get_pe('warning')} خطا.", show_alert=True)


# ─── Interactive delivery: fetch SMS / cancel + auto-refund ──────────

@router.callback_query(F.data.startswith("vsms:"))
async def cb_virtual_get_sms(callback: CallbackQuery) -> None:
    """Fetch the SMS code on demand for a delivered Kalino number."""
    try:
        await callback.answer()
        parts = callback.data.split(":")
        if len(parts) < 4:
            await callback.answer(f"{get_pe('warning')} داده نامعتبر.", show_alert=True)
            return

        order_id, request_id, number = parts[1], parts[2], parts[3]

        from database.db import get_order
        order = await get_order(int(order_id))
        if order and int(order["user_id"]) != callback.from_user.id:
            await callback.answer(f"{get_pe('forbidden')} این سفارش متعلق به شما نیست.", show_alert=True)
            return

        await callback.answer(f"{get_pe('hourglass')} در حال دریافت کد تأیید…")

        from utils.kalino import get_number_code
        result = await get_number_code(request_id)

        code = result.get("code") if result else None
        waiting = result.get("waiting", False) if result else False

        if code:
            text = (
                f"{get_pe('check')} <b>کد تایید دریافت شد!</b>\n\n"
                f"{get_pe('mobile')} شماره: <code>{number}</code>\n"
                f"{get_pe('key_icon')} کد تایید: <code>{code}</code>\n\n"
                "از این کد برای فعال‌سازی حساب خود استفاده کنید.\n"
                f"{get_pe('warning')} این کد محرمانه است؛ آن را با کسی به اشتراک نگذارید."
            )
            kb = InlineKeyboardMarkup(inline_keyboard=[
                [InlineKeyboardButton(
                    text=f"{get_plain_emoji('cross')} لغو سفارش و بازگشت وجه",
                    callback_data=f"vcnl:{order_id}:{request_id}:{number}")],
                [InlineKeyboardButton(
                    text=f"{get_plain_emoji('back_arrow')} بازگشت به منو",
                    callback_data="menu:back_main")],
            ])
            await callback.message.edit_text(text, reply_markup=kb)
        elif waiting:
            await callback.answer(
                f"{get_pe('hourglass')} کد هنوز نرسیده است. حدود ۱ تا ۲ دقیقه صبر کنید و دوباره دکمه را بزنید.",
            )
        else:
            await callback.answer(
                f"{get_pe('warning')} کدی یافت نشد. شماره ممکن است منقضی شده باشد؛ با پشتیبانی در تماس باشید.",
                show_alert=True,
            )
    except Exception as e:
        logger.error(f"CRASH IN VSMS: {e}", exc_info=True)
        await callback.answer(f"{get_pe('warning')} خطایی رخ داد.", show_alert=True)


@router.callback_query(F.data.startswith("vcnl:"))
async def cb_virtual_cancel_delivered(callback: CallbackQuery) -> None:
    """Cancel a delivered Kalino number via API and auto-refund the wallet."""
    try:
        await callback.answer()
        parts = callback.data.split(":")
        if len(parts) < 4:
            await callback.answer(f"{get_pe('warning')} داده نامعتبر.", show_alert=True)
            return

        order_id, request_id, number = parts[1], parts[2], parts[3]

        from database.db import get_order, add_to_wallet, update_order_status
        order = await get_order(int(order_id))
        if not order:
            await callback.answer(f"{get_pe('warning')} سفارش یافت نشد.", show_alert=True)
            return
        if int(order["user_id"]) != callback.from_user.id:
            await callback.answer(f"{get_pe('forbidden')} این سفارش متعلق به شما نیست.", show_alert=True)
            return
        if order["status"] == "cancelled":
            await callback.answer("این سفارش قبلاً لغو شده است.", show_alert=True)
            return

        await callback.answer(f"{get_pe('hourglass')} در حال لغو سفارش و بازگشت وجه…")

        from utils.kalino import cancel_virtual_number
        api_result = await cancel_virtual_number(request_id)

        refund_amount = int(order["amount_irt"] or 0)
        if refund_amount > 0:
            await add_to_wallet(callback.from_user.id, refund_amount)
        await update_order_status(order_id, "cancelled")

        api_status = (
            f"{get_pe('heavy_check')} شماره از پنل حذف شد."
            if (api_result and api_result.get("success")) else
            f"{get_pe('warning')} حذف از پنل ناموفق بود؛ در صورت کسر وجه با پشتیبانی تماس بگیرید."
        )

        refund_str = f"{refund_amount:,}".replace(",", "،")
        await callback.message.edit_text(
            f"{get_pe('cross')} <b>سفارش لغو شد و وجه بازگشت داده شد.</b>\n\n"
            f"{get_pe('mobile')} شماره: <code>{number}</code>\n"
            f"{get_pe('money')} مبلغ بازگشتی: <b>{refund_str} تومان</b>\n"
            f"{api_status}\n\n"
            f"{get_pe('purse')} مبلغ به <b>کیف پول شما در ربات</b> اضافه شد.\n"
            "می‌توانید برای خرید دیگری استفاده کنید.",
            reply_markup=back_to_menu_kb(),
        )

        # Notify admins about the cancellation + refund
        from config import config as _cfg
        for admin_id in _cfg.ADMIN_IDS:
            with contextlib.suppress(Exception):
                username_display = (
                    f"@{callback.from_user.username}" if callback.from_user.username else "ندارد"
                )
                await callback.message.bot.send_message(
                    admin_id,
                    f"{get_pe('siren')} <b>لغو سفارش #{order_id}</b> (شماره مجازی)\n"
                    f"{get_pe('mobile')} شماره: <code>{number}</code> | شناسه پنل: <code>{request_id}</code>\n"
                    f"{get_pe('user')} کاربر: <code>{callback.from_user.id}</code> ({username_display})\n"
                    f"{get_pe('money')} مبلغ بازگشتی به کیف پول: <b>{refund_str} تومان</b>\n"
                    f"دلیل: درخواست لغو توسط کاربر",
                )
    except Exception as e:
        logger.error(f"CRASH IN VCNL: {e}", exc_info=True)
        await callback.answer(f"{get_pe('warning')} خطایی رخ داد.", show_alert=True)


# ─── Market Rates (Live Exchange Rates) ─────────────────────────────

@router.callback_query(F.data == "menu:market_rates")
async def cb_market_rates(callback: CallbackQuery) -> None:
    await callback.answer()
    from utils.pricing import get_market_data

    data = await get_market_data()
    if not data:
        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('warning')} دریافت اطلاعات بازار با مشکل مواجه شد.\nلطفاً دوباره تلاش کنید.",
                reply_markup=market_rates_refresh_kb(),
            )
        await callback.answer()
        return

    lines = [f"{get_pe('chart')} <b>قیمت لحظه‌ای ارزها و طلا</b>\n"]

    def build_lookup(category: str) -> dict[str, dict]:
        """Index one BrsApi root array by its exact symbol value."""
        return {
            item["symbol"]: item
            for item in data.get(category, [])
            if isinstance(item, dict) and item.get("symbol")
        }

    gold = build_lookup("gold")
    currency = build_lookup("currency")
    cryptocurrency = build_lookup("cryptocurrency")

    def as_number(value: object) -> float:
        """Convert API numbers, including numeric strings, to floats."""
        if value is None:
            return 0.0
        try:
            return float(str(value).replace(",", "").replace("،", "").strip())
        except (TypeError, ValueError):
            return 0.0

    def format_number(value: object) -> str:
        number = as_number(value)
        if number.is_integer():
            return f"{int(number):,}"
        return f"{number:,.8f}".rstrip("0").rstrip(".")

    def format_item(
        items: dict[str, dict],
        symbol: str,
        name: str,
        unit: str,
    ) -> str:
        item = items.get(symbol)
        if not item:
            return f"  {name}: {get_pe('warning')} موجود نیست"

        price = item.get("price")
        change = as_number(item.get("change_percent"))
        change_icon = get_pe("arrow_up") if change >= 0 else get_pe("arrow_down")
        return (
            f"  {name}: <b>{format_number(price)}</b> {unit} "
            f"{change_icon} {change:+.2f}%"
        )

    # ── Popular Currencies (prices are in Toman) ──
    lines.append(f"{get_pe('money')} <b>ارزهای پرکاربرد:</b>")
    lines.append(format_item(currency, "USD", f"{get_pe('flag_us')} دلار آمریکا", "تومان"))
    lines.append(format_item(currency, "EUR", f"{get_pe('flag_eu')} یورو", "تومان"))
    lines.append(format_item(currency, "USDT_IRT", f"{get_pe('coin_new')} تتر", "تومان"))
    lines.append("")

    # ── Gold & Coins (prices are in Toman) ──
    lines.append(f"{get_pe('coin_new')} <b>طلا و سکه:</b>")
    lines.append(format_item(gold, "IR_GOLD_18K", f"{get_pe('medal_gold')} طلای ۱۸ عیار", "تومان"))
    lines.append(format_item(gold, "IR_COIN_EMAMI", f"{get_pe('coin_new')} سکه امامی", "تومان"))
    lines.append(format_item(gold, "IR_COIN_BAHAR", f"{get_pe('coin_new')} سکه بهار آزادی", "تومان"))
    lines.append("")

    # ── Cryptocurrencies (prices are in USD) ──
    lines.append(f"{get_pe('diamond')} <b>رمزارزهای اصلی:</b>")
    lines.append(format_item(cryptocurrency, "BTC", f"{get_pe('coin')} بیت‌کوین", "USD"))
    lines.append(format_item(cryptocurrency, "ETH", f"{get_pe('diamond')} اتریوم", "USD"))
    lines.append(format_item(cryptocurrency, "SOL", f"{get_pe('sparkles')} سولانا", "USD"))
    lines.append("")
    lines.append(f"{get_pe('clock')} نرخ‌ها لحظه‌ای هستند و هر ۵ دقیقه به‌روز می‌شوند.")

    with contextlib.suppress(TelegramBadRequest):
        await callback.message.edit_text(
            "\n".join(lines),
            reply_markup=market_rates_refresh_kb(),
        )
    await callback.answer()


# ─── My Orders (پیگیری سفارشات) ─────────────────────────────────────

@router.callback_query(F.data == "menu:my_orders")
async def cb_my_orders(callback: CallbackQuery) -> None:
    await callback.answer()
    orders = await get_user_orders(callback.from_user.id)
    if not orders:
        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                f"{get_pe('ticket')} <b>پیگیری سفارشات</b>\n\nشما هنوز سفارشی ثبت نکرده‌اید.",
                reply_markup=back_to_menu_kb(),
            )
    else:
        lines = [f"{get_pe('ticket')} <b>پیگیری سفارشات</b>\n"]
        for o in orders[:10]:  # last 10
            lines.append(_format_order_line(o))
        lines.append("\nبرای اطلاعات بیشتر روی شماره سفارش کلیک کنید.")
        with contextlib.suppress(TelegramBadRequest):
            await callback.message.edit_text(
                "\n".join(lines),
                reply_markup=back_to_menu_kb(),
            )
    await callback.answer()


def _format_order_line(o) -> str:
    """Format a single order line with detailed status."""
    status_map = {
        "pending":   (get_pe("yellow_circle"), "در انتظار پرداخت"),
        "paid":      (get_pe("green_circle"), "پرداخت شده / در صف انجام"),
        "delivered": (get_pe("check"), "انجام شده"),
        "cancelled": (get_pe("cross"), "لغو شده"),
    }
    emoji, label = status_map.get(o["status"], (get_pe("question"), o["status"]))
    amount = f"{o['amount_irt']:,}".replace(",", "،") if o["amount_irt"] else "—"
    return f"{emoji} #{o['order_id']} | {o['product']} | {amount} تومان | {label}"


def _status_fa(status: str) -> str:
    """Convert order status to Persian."""
    return {
        "pending": "در انتظار",
        "paid": "پرداخت شده",
        "delivered": "تحویل شده",
        "cancelled": "لغو شده",
    }.get(status, status)
