import logging
import asyncio
import html
from io import BytesIO
from datetime import datetime

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto
from telegram.error import BadRequest
from telegram.ext import ContextTypes

log = logging.getLogger("bot.button")
from config import QRIS_PATH, BANNER_PATH
from services.payment_info_service import (
    get_payment_info,
    get_qris_file_id,
    get_main_caption,
    reset_main_caption,
    set_qris as save_qris,
    set_dana as save_dana,
)
from services.promo_service import get_promo, use_promo
from services.payment_service import (
    WARNING_ICON,
    send_approval_to_admin,
    update_all_approval_messages,
    process_approval,
    process_rejection,
    post_testimonial_to_channels,
)
from services.backup_service import create_database_backup
from services.transaction_service import (
    get_user_approved_vip_transactions,
    get_user_total_approved_payment,
    save_transaction,
)
from utils.menu_view import get_main_menu_text, show_main_menu
from utils.custom_emoji import (
    DANA_BUTTON_ICON_ID,
    QRIS_BUTTON_ICON_ID,
    TELEGRAM_CAPTION_LIMIT,
    parse_mode_for_custom_emoji_html,
    premium_button_icon_kwargs,
    visible_text_length,
)
from utils.admin_transaction_report import (
    build_transaction_csv,
    format_owner_transaction_dashboard,
    format_period_transaction_report,
    format_recent_transaction_report,
)
from services.vs_service import get_vs
from services.admin_service import get_vs_enabled_admins

from services.auth_service import is_admin, is_owner
from services.maintenance_service import get_maintenance, set_maintenance
from services.user_service import get_all_users, get_user_by_id, save_user
from data.memory import (
    PROCESSED_APPROVALS,
    REJOIN_PENDING,
    OPENED_CONTACT_IDS,
    OPENED_ORDER_IDS,
    OPENED_REJOIN_IDS,
)
from data.pending_store import (
    add_pending_message,
    get_all_pending,
    get_pending,
    get_pending_count,
)
import os
import re
import random
import string
from services.promo_service import get_all_promos
from services.link_service import (
    get_links_grouped_by_group, get_links_by_chat_id, get_links_by_group_name,
    get_links_by_user_id, delete_link_by_id, get_link_by_id, add_link
)
from services.vip_service import (
    get_all_packages,
    get_visible_packages,
    get_package_by_id,
    delete_package,
    update_package,
    toggle_package_visibility,
    is_package_visible,
    clear_package_banner,
    clear_package_caption,
)
from config import GROUP_IDS
from handlers.broadcast_handler import handle_broadcast_buttons
from services.admin_service import get_admin_by_id
from services.channel_service import (
    get_all_channels,
    get_channel_by_id,
    add_channel,
    update_channel,
    delete_channel,
)
from services.connected_channel_service import (
    get_all_connected_channels,
    get_connected_channel_by_id,
    add_connected_channel,
    update_connected_channel,
    delete_connected_channel,
    get_connected_channel_chat,
    get_connected_channel_member_count,
    check_channel_connection,
)
from services.contact_service import (
    close_contact_message,
    delete_contact_message,
    get_contact_history,
    get_contact_message,
    get_contact_messages,
    get_contact_status_counts,
    get_open_contact_count,
    mark_contact_replied,
)
from data.memory import (
    VS_PENDING_APPROVALS,
    VS_PROCESSED_APPROVALS,
    VS_APPROVED_CACHE
)
from db.database import cursor

CONTACT_TOPICS = {
    "payment": "Pembayaran",
    "access": "Akses Grup",
    "other": "Lainnya",
}

CONTACT_STATUS_LABELS = {
    "open": "🔴 Open",
    "replied": "🟡 Replied",
    "closed": "🟢 Closed",
}

CONTACT_QUICK_REPLIES = {
    "wait": "Mohon tunggu sebentar ya, admin sedang cek data kamu.",
    "proof": "Tolong kirim bukti pembayaran yang lebih jelas agar bisa kami cek.",
    "done": "Sudah kami bantu proses. Jika masih ada kendala, balas pesan ini ya.",
}

REJECTION_REASONS = {
    "bad_photo": "Bukti tidak jelas",
    "wrong_amount": "Nominal salah",
    "invalid_proof": "Bukti tidak valid",
    "other": "Lainnya",
}


async def _edit_query_text(query, text, reply_markup=None, parse_mode="HTML"):
    if query.message and (query.message.photo or query.message.video):
        try:
            await query.message.delete()
        except Exception:
            pass
        return await query.message.reply_text(
            text=text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )

    try:
        return await query.edit_message_text(
            text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )
    except BadRequest as exc:
        if "There is no text in the message to edit" not in str(exc):
            raise
        if not query.message:
            raise
        return await query.message.reply_text(
            text=text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )


async def _edit_query_caption_or_text(query, text, reply_markup=None, parse_mode="HTML"):
    if query.message and (query.message.photo or query.message.video):
        return await query.edit_message_caption(
            caption=text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )

    return await _edit_query_text(
        query,
        text,
        reply_markup=reply_markup,
        parse_mode=parse_mode
    )


async def _replace_query_with_text(query, context, text, reply_markup=None, parse_mode="HTML"):
    if query.message and (query.message.photo or query.message.video):
        try:
            await query.message.delete()
        except Exception:
            pass
        return await context.bot.send_message(
            chat_id=query.from_user.id,
            text=text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )

    try:
        await query.edit_message_text(
            text=text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )
        return query.message
    except Exception:
        return await context.bot.send_message(
            chat_id=query.from_user.id,
            text=text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )


def _contact_inbox_keyboard(messages):
    keyboard = []
    for item in messages:
        name = item["first_name"] or "User"
        topic = item["topic"] or "-"
        status = item["status"] or "open"
        if status == "closed":
            icon = "🟢"
        elif status == "replied" or item["id"] in OPENED_CONTACT_IDS:
            icon = "🟡"
        else:
            icon = "🔴"
        keyboard.append([
            InlineKeyboardButton(
                f"{icon} {name} - {topic}",
                callback_data=f"owner_contact_open_{item['id']}"
            )
        ])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_notifications")])
    return InlineKeyboardMarkup(keyboard)


async def _show_contact_inbox(query):
    messages = get_contact_messages()
    counts = get_contact_status_counts()
    open_count = counts.get("open", 0)
    replied_count = counts.get("replied", 0)
    closed_count = counts.get("closed", 0)

    if not messages:
        await _edit_query_text(
            query,
            "📩 <b>Pesan Masuk</b>\n\nBelum ada tiket bantuan.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_notifications")]
            ])
        )
        return

    text = (
        "📩 <b>Pesan Masuk</b>\n\n"
        f"🔴 Open: <b>{open_count}</b>\n"
        f"🟡 Replied: <b>{replied_count}</b>\n"
        f"🟢 Closed: <b>{closed_count}</b>\n\n"
        "Pilih tiket yang ingin dibuka:"
    )
    await _edit_query_text(query, text, reply_markup=_contact_inbox_keyboard(messages))


async def _show_contact_detail(query, message_id):
    item = get_contact_message(message_id)
    if not item:
        await query.answer("Pesan tidak ditemukan.", show_alert=True)
        await _show_contact_inbox(query)
        return

    username = f"@{item['username']}" if item["username"] else "-"
    status = item["status"] or "open"
    status_label = CONTACT_STATUS_LABELS.get(status, status)
    history = get_contact_history(item["user_id"], limit=4)
    history_lines = []
    for row in history:
        marker = "•" if row["id"] == message_id else "◦"
        history_lines.append(
            f"{marker} {html.escape(row['created_at'] or '-')} - "
            f"{html.escape(row['topic'] or '-')} ({html.escape(row['status'] or '-')})"
        )
    history_text = "\n".join(history_lines) if history_lines else "-"
    text = (
        "📩 <b>Detail Pesan</b>\n\n"
        f"User: <b>{html.escape(item['first_name'] or 'User')}</b>\n"
        f"Username: {html.escape(username)}\n"
        f"ID: <code>{item['user_id']}</code>\n"
        f"Topik: <b>{html.escape(item['topic'] or '-')}</b>\n"
        f"Status: <b>{html.escape(status_label)}</b>\n"
        f"Waktu: {html.escape(item['created_at'] or '-')}\n\n"
        f"Pesan:\n{html.escape(item['message'] or '-')}\n\n"
        f"Riwayat User:\n{history_text}"
    )
    keyboard = [
        [
            InlineKeyboardButton("💬 Balas", callback_data=f"owner_contact_reply_{message_id}"),
            InlineKeyboardButton("✅ Tutup Tiket", callback_data=f"owner_contact_close_{message_id}"),
        ],
        [
            InlineKeyboardButton("⚡ Quick Reply", callback_data=f"owner_contact_quick_{message_id}"),
            InlineKeyboardButton("🗑️ Hapus", callback_data=f"owner_contact_delete_{message_id}"),
        ],
        [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_contact_inbox")]
    ]
    await _edit_query_text(query, text, reply_markup=InlineKeyboardMarkup(keyboard))


def _format_rupiah(value):
    try:
        if isinstance(value, str):
            value = value.replace(".", "").replace(",", "").strip()
        return f"Rp {int(value):,}".replace(",", ".")
    except (TypeError, ValueError):
        return "-"


def _short_text(value, limit=44):
    text = str(value or "-").replace("\n", " ")
    if len(text) <= limit:
        return text
    return text[:limit - 3] + "..."


def _pending_user_name(user_id, data):
    user_data = data.get("user", {}) if isinstance(data, dict) else {}
    return user_data.get("first_name") or f"User {user_id}"


def _abbreviate(name):
    name = name.upper().strip()
    if not name:
        return "XXX"
    result = name[0]
    for ch in name[1:]:
        if ch not in "AIUEO":
            result += ch
        if len(result) >= 4:
            break
    return result


def _generate_transaction_code(order, user_id):
    today = datetime.now().strftime("%y%m%d")
    user_part = str(user_id)[-4:].zfill(4)
    rand_part = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))

    paket_type = order.get("paket_type")
    if paket_type == "take_several":
        prefix = "MLT"
    elif paket_type == "take_all":
        prefix = "ALL"
    elif paket_type == "vs":
        prefix = "VS"
    elif paket_type == "rejoin":
        prefix = "RJ"
    else:
        selected_ids = order.get("selected_ids") or []
        pkg = get_package_by_id(selected_ids[0]) if selected_ids else None
        prefix = _abbreviate(pkg["nama"]) if pkg else "VIP"

    return f"{prefix}-{today}-{user_part}-{rand_part}"


def _get_payment_package_label(context):
    if context.user_data.get("vs_status") == "waiting_payment":
        return f"VCS - {context.user_data.get('vs_paket', '-')}"

    paket_type = context.user_data.get("paket_type", "single")
    selected_ids = context.user_data.get("vip_selected_ids", [])
    if paket_type == "single" and selected_ids:
        pkg = get_package_by_id(selected_ids[0])
        return pkg["nama"] if pkg else "Paket VIP"
    if paket_type == "take_several":
        return f"Take Beberapa ({len(selected_ids)} Paket)"
    if paket_type == "take_all":
        return "Take All"
    return "Paket VIP"


def _get_payment_total(context):
    if context.user_data.get("vs_status") == "waiting_payment":
        return context.user_data.get("vs_harga_bayar", 0)
    return context.user_data.get("vip_harga_bayar", 0)


def _ensure_payment_transaction_code(context, user_id):
    transaction_code = context.user_data.get("transaction_code")
    if transaction_code:
        return transaction_code

    paket_type = context.user_data.get("paket_type")
    if context.user_data.get("vs_status") == "waiting_payment":
        paket_type = "vs"

    transaction_code = _generate_transaction_code({
        "paket_type": paket_type,
        "selected_ids": context.user_data.get("vip_selected_ids", []),
    }, user_id)
    context.user_data["transaction_code"] = transaction_code
    return transaction_code


def _build_payment_invoice(context, user_id, method):
    transaction_code = _ensure_payment_transaction_code(context, user_id)
    package_label = html.escape(_get_payment_package_label(context))
    total = _format_rupiah(_get_payment_total(context))

    return (
        "🧾 <b>Invoice Pembayaran</b>\n\n"
        f"Kode Transaksi: <code>{transaction_code}</code>\n"
        f"Paket: <b>{package_label}</b>\n"
        f"Total Bayar: <b>{total}</b>\n"
        f"Metode: <b>{html.escape(method)}</b>\n\n"
        "Silakan bayar sesuai nominal.\n"
        "Setelah transfer, kirim foto bukti pembayaran.\n\n"
        "⚠️ Pastikan nominal sesuai invoice.\n"
        "⚠️ Bukti buram/tidak jelas bisa ditolak."
    )


def _format_order_detail(user_id, data):
    user_data = data.get("user", {}) if isinstance(data, dict) else {}
    order = data.get("order", {}) if isinstance(data, dict) else {}

    first_name = user_data.get("first_name") or f"User {user_id}"
    username = user_data.get("username")
    username_text = f"@{username}" if username else "Tidak ada"
    paket_label = str(order.get("paket_label", "Tidak diketahui") or "Tidak diketahui")
    if len(paket_label) > 800:
        paket_label = paket_label[:797] + "..."
    harga_bayar = _format_rupiah(order.get("harga_bayar"))
    promo = order.get("promo_used") or "-"
    created_at = data.get("created_at", "-") if isinstance(data, dict) else "-"
    trx_code = order.get("transaction_code") or "-"

    return (
        "📦 <b>Detail Order</b>\n\n"
        f"Kode: <code>{html.escape(trx_code)}</code>\n"
        f"User: <b>{html.escape(first_name)}</b>\n"
        f"Username: {html.escape(username_text)}\n"
        f"ID: <code>{user_id}</code>\n"
        f"Waktu: {html.escape(str(created_at))}\n\n"
        f"Paket:\n{html.escape(paket_label)}\n\n"
        f"Total Bayar:\n<b>{html.escape(harga_bayar)}</b>\n\n"
        f"Promo:\n{html.escape(str(promo))}\n\n"
        "Status:\n<b>Menunggu Approval</b>\n\n"
        "📸 Bukti pembayaran terlampir."
    )


def _order_detail_keyboard(user_id, include_chat=True):
    keyboard = [
        [
            InlineKeyboardButton("✅ Approve", callback_data=f"approve_{user_id}"),
            InlineKeyboardButton("❌ Tolak", callback_data=f"reject_{user_id}")
        ],
    ]
    if include_chat:
        keyboard.append([InlineKeyboardButton("💬 Chat User", url=f"tg://user?id={user_id}")])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_order_inbox")])
    return InlineKeyboardMarkup(keyboard)


def _is_button_privacy_error(exc):
    return "button_user_privacy_restricted" in str(exc).lower()


def _order_inbox_keyboard(pending_orders):
    keyboard = []
    ordered_items = sorted(
        pending_orders.items(),
        key=lambda item: item[1].get("created_at", "") if isinstance(item[1], dict) else "",
        reverse=True
    )

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

        order = data.get("order", {})
        name = _short_text(_pending_user_name(user_id, data), 18)
        paket = _short_text(order.get("paket_label", "Order paket"), 32)
        status = "🟡" if user_id in OPENED_ORDER_IDS else "🔴"
        keyboard.append([
            InlineKeyboardButton(
                f"{status} 👤 {name} - {paket}",
                callback_data=f"owner_order_open_{user_id}"
            )
        ])

    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_notifications")])
    return InlineKeyboardMarkup(keyboard)


def _rejoin_inbox_keyboard():
    """Create keyboard for rejoin requests list."""
    keyboard = []
    
    for user_id, data in REJOIN_PENDING.items():
        if not isinstance(data, dict):
            continue
        
        name = _short_text(data.get("first_name", "User"), 18)
        status = "🟡" if user_id in OPENED_REJOIN_IDS else "🔴"
        keyboard.append([
            InlineKeyboardButton(
                f"{status} 👤 {name} - Rejoin Request",
                callback_data=f"rejoin_open_{user_id}"
            )
        ])
    
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_notifications")])
    return InlineKeyboardMarkup(keyboard)


async def _show_order_inbox(query):
    pending_orders = get_all_pending()
    count = len(pending_orders)

    if not pending_orders:
        await _edit_query_text(
            query,
            "📦 <b>Order Masuk</b>\n\nBelum ada order pending.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_notifications")]
            ])
        )
        return

    text = (
        "📦 <b>Order Masuk</b>\n\n"
        f"Total order pending: <b>{count}</b>\n"
        "Pilih order yang ingin dibuka:"
    )
    await _edit_query_text(query, text, reply_markup=_order_inbox_keyboard(pending_orders))


async def _show_order_detail(query, context, user_id):
    data = get_pending(user_id)
    if not data:
        await query.answer("Order tidak ditemukan atau sudah diproses.", show_alert=True)
        await _show_order_inbox(query)
        return

    keyboard = _order_detail_keyboard(user_id)
    caption = _format_order_detail(user_id, data)
    photo_id = data.get("photo_id")

    if photo_id:
        try:
            sent = await context.bot.send_photo(
                chat_id=query.message.chat_id,
                photo=photo_id,
                caption=caption,
                parse_mode="HTML",
                reply_markup=keyboard
            )
        except BadRequest as exc:
            if not _is_button_privacy_error(exc):
                raise
            sent = await context.bot.send_photo(
                chat_id=query.message.chat_id,
                photo=photo_id,
                caption=caption,
                parse_mode="HTML",
                reply_markup=_order_detail_keyboard(user_id, include_chat=False)
            )
            await query.answer("Detail dibuka tanpa tombol Chat User karena privasi user.")
        add_pending_message(user_id, {
            "chat_id": query.message.chat_id,
            "message_id": sent.message_id,
            "kind": "approval",
        })
        return

    try:
        await _edit_query_text(query, caption, reply_markup=keyboard)
    except BadRequest as exc:
        if not _is_button_privacy_error(exc):
            raise
        await _edit_query_text(
            query,
            caption,
            reply_markup=_order_detail_keyboard(user_id, include_chat=False)
        )
        await query.answer("Detail dibuka tanpa tombol Chat User karena privasi user.")
    add_pending_message(user_id, {
        "chat_id": query.message.chat_id,
        "message_id": query.message.message_id,
        "kind": "approval_text",
    })


async def _edit_order_message(query, text, reply_markup=None, parse_mode="HTML"):
    if query.message and (query.message.photo or query.message.video):
        await query.edit_message_caption(
            caption=text,
            parse_mode=parse_mode,
            reply_markup=reply_markup
        )
        return

    await _edit_query_text(query, text, reply_markup=reply_markup, parse_mode=parse_mode)


async def _show_rejection_reasons(query, context, user_id):
    data = get_pending(user_id)
    if not data:
        await query.answer("Order tidak ditemukan atau sudah diproses.", show_alert=True)
        return

    order = data.get("order", {}) if isinstance(data, dict) else {}
    trx_code = order.get("transaction_code") or "-"
    context.user_data[f"reject_caption_{user_id}"] = (
        query.message.caption
        or query.message.text
        or _format_order_detail(user_id, data)
    )
    keyboard = InlineKeyboardMarkup([
        [InlineKeyboardButton("📷 Bukti tidak jelas", callback_data=f"reject_reason_{user_id}_bad_photo")],
        [InlineKeyboardButton("💰 Nominal salah", callback_data=f"reject_reason_{user_id}_wrong_amount")],
        [InlineKeyboardButton("🚫 Bukti tidak valid", callback_data=f"reject_reason_{user_id}_invalid_proof")],
        [InlineKeyboardButton("📝 Lainnya", callback_data=f"reject_reason_{user_id}_other")],
        [InlineKeyboardButton("⬅️ Kembali Detail", callback_data=f"owner_order_open_{user_id}")],
    ])
    await _edit_order_message(
        query,
        "❌ <b>Tolak Order</b>\n\n"
        f"Kode: <code>{html.escape(str(trx_code))}</code>\n\n"
        "Pilih alasan penolakan yang akan dikirim ke user:",
        reply_markup=keyboard
    )


def _get_package_links(pkg):
    links = get_links_by_chat_id(pkg["group_id"]) if pkg.get("group_id") else []
    if not links:
        links = get_links_by_group_name(pkg["nama"])
    return links


USER_LIST_PER_PAGE = 50

async def _show_user_list_page(query, context):
    data = context.user_data.get("owner_user_list")
    if not data:
        await query.answer("❌ Sesi habis, silakan pilih menu lagi.", show_alert=True)
        return

    rows = data["rows"]
    page = data["page"]
    list_type = data["type"]
    total = len(rows)
    start = page * USER_LIST_PER_PAGE
    end = min(start + USER_LIST_PER_PAGE, total)
    has_more = end < total

    title_map = {"start_join": "👥 User Start + Join", "join_paket": "📋 User Join Paket", "start_only": "🆕 User Start Saja"}
    title = title_map.get(list_type, "📋 User")

    page_info = f" (Halaman {page + 1})" if has_more or page > 0 else ""
    lines = [f"<b>{title} ({total})</b>{page_info}\n"]
    for r in rows[start:end]:
        name = html.escape(r["first_name"] or "-")
        un = f"@{r['username']}" if r["username"] else "-"
        if list_type == "join_paket":
            pkg = html.escape(r["package_label"] or "-")
            lines.append(f"• {r['user_id']} | {name} | {un} → {pkg}")
        else:
            lines.append(f"• {r['user_id']} | {name} | {un}")

    text = "\n".join(lines)
    if len(text) > 4000:
        text = text[:3997] + "..."

    keyboard = []
    if has_more:
        data["page"] = page + 1
        keyboard.append([InlineKeyboardButton("📄 Lihat Lainnya", callback_data="owner_user_list_page")])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_user_list")])

    await _edit_query_text(query, text, reply_markup=InlineKeyboardMarkup(keyboard))


async def _show_member_dashboard(query, context, pkg_id):
    pkg = get_package_by_id(pkg_id)
    if not pkg:
        await query.answer("❌ Paket tidak ditemukan.", show_alert=True)
        return

    if pkg.get("group_id"):
        try:
            total_telegram = await context.bot.get_chat_member_count(pkg["group_id"])
        except Exception:
            total_telegram = "⚠️ Gagal"
    else:
        total_telegram = "🔗 Link statis"

    links = _get_package_links(pkg)
    keyboard = InlineKeyboardMarkup([
        [InlineKeyboardButton("👤 Lihat User", callback_data=f"listlink_userid_{pkg_id}")],
        [InlineKeyboardButton("🔗 Lihat Link", callback_data=f"listlink_links_{pkg_id}")],
        [InlineKeyboardButton("🔍 Cari User", callback_data=f"listlink_search_{pkg_id}")],
        [InlineKeyboardButton("➕ Tambah Manual", callback_data=f"listlink_tambah_{pkg_id}")],
        [InlineKeyboardButton("⬅️ Kembali", callback_data="admin_infogrup")]
    ])
    await _edit_query_text(
        query,
        f"👥 <b>Member {html.escape(pkg['nama'])}</b>\n\n"
        f"Total Telegram: <b>{total_telegram}</b>\n"
        f"Terdaftar via bot: <b>{len(links)}</b>\n"
        f"*{total_telegram} = total anggota di channel Telegram — termasuk yang join manual, diundang admin, dll.",
        reply_markup=keyboard
    )


async def _show_member_picker(query, pkg_id, mode="user"):
    pkg = get_package_by_id(pkg_id)
    if not pkg:
        await query.answer("❌ Paket tidak ditemukan.", show_alert=True)
        return

    links = _get_package_links(pkg)
    title = "👤 User" if mode == "user" else "🔗 Link"
    if not links:
        await _edit_query_text(
            query,
            f"{title} — <b>{html.escape(pkg['nama'])}</b>\n\n"
            "📭 Belum ada member aktif di paket ini.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_paket_{pkg_id}")]
            ])
        )
        return

    keyboard = []
    for item in links:
        un = item.get("username") or "-"
        uname = f"@{un}" if un and un != "-" else "-"
        label = f"👤 {item['user_id']} | {uname}" if mode == "user" else f"🔗 {item.get('link') or '-'}"
        keyboard.append([
            InlineKeyboardButton(
                _short_text(label, 56),
                callback_data=f"listlink_member_{item['id']}_{pkg_id}_{mode}"
            )
        ])
    keyboard.append([InlineKeyboardButton("🔍 Cari User", callback_data=f"listlink_search_{pkg_id}")])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_paket_{pkg_id}")])

    await _edit_query_text(
        query,
        f"{title} — <b>{html.escape(pkg['nama'])}</b>\n\n" +
        ("Pilih member untuk membuka detail." if mode == "user" else f"Total {len(links)} link. Pilih link untuk membuka detail member."),
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


async def _show_member_detail(query, row_id, pkg_id, mode="user"):
    item = get_link_by_id(int(row_id))
    pkg = get_package_by_id(pkg_id)
    if not item or item.get("status") != "Aktif":
        await query.answer("Data member tidak ditemukan atau sudah tidak aktif.", show_alert=True)
        return

    username = item.get("username") or "-"
    username_text = f"@{username}" if username and username != "-" else "-"
    group_name = pkg["nama"] if pkg else item.get("group_name", "-")
    package_links = _get_package_links(pkg) if pkg else [item]
    user_links = [
        link_item for link_item in package_links
        if str(link_item.get("user_id")) == str(item.get("user_id"))
        and link_item.get("status") == "Aktif"
    ]
    if not any(str(link_item.get("id")) == str(item.get("id")) for link_item in user_links):
        user_links.insert(0, item)

    links_to_show = user_links[:15]
    link_lines = []
    for index, link_item in enumerate(links_to_show, 1):
        link_value = link_item.get("link") or "-"
        join_date = html.escape(str(link_item.get("join_date") or "-"))
        link_lines.append(f"{index}. <code>{html.escape(link_value)}</code>\n   Dibuat: {join_date}")
    if len(user_links) > len(links_to_show):
        link_lines.append(f"... dan {len(user_links) - len(links_to_show)} link lainnya.")
    group_links_text = "\n".join(link_lines) if link_lines else "-"

    text = (
        "👤 <b>Detail Member</b>\n\n"
        f"Nama: <b>{html.escape(username_text if username_text != '-' else 'User')}</b>\n"
        f"Username: {html.escape(username_text)}\n"
        f"ID: <code>{item['user_id']}</code>\n"
        f"Paket: <b>{html.escape(group_name)}</b>\n"
        f"Status Link: <b>{html.escape(item.get('status') or '-')}</b>\n"
        f"Total Link Paket Ini: <b>{len(user_links)}</b>\n"
        f"Link Grup Dibuat:\n{group_links_text}"
    )
    keyboard = []
    if is_owner(query.from_user.id):
        keyboard.append([InlineKeyboardButton("🔄 Kirim Ulang Link", callback_data=f"listlink_resend_{row_id}_{pkg_id}")])
        keyboard.append([InlineKeyboardButton("🗑 Hapus Akses", callback_data=f"listlink_hapus_member_{row_id}_{pkg_id}")])
    valid_group_links = [
        link_item.get("link") for link_item in user_links
        if str(link_item.get("link") or "").startswith(("http://", "https://", "tg://"))
    ]
    for index, link_value in enumerate(valid_group_links[:5], 1):
        label = "🔗 Buka Link Grup" if len(valid_group_links) == 1 else f"🔗 Buka Link {index}"
        keyboard.append([InlineKeyboardButton(label, url=link_value)])
    keyboard.append([InlineKeyboardButton("💬 Chat User", url=f"tg://user?id={item['user_id']}")])
    back_callback = f"listlink_links_{pkg_id}" if mode == "link" else f"listlink_userid_{pkg_id}"
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data=back_callback)])
    await _edit_query_text(query, text, reply_markup=InlineKeyboardMarkup(keyboard))


async def _show_rejoin_inbox(query):
    """Display rejoin requests list."""
    count = len(REJOIN_PENDING)

    if not REJOIN_PENDING:
        await _edit_query_text(
            query,
            "🔄 <b>Rejoin Requests</b>\n\nBelum ada permintaan rejoin pending.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_notifications")]
            ])
        )
        return

    text = (
        "🔄 <b>Rejoin Requests</b>\n\n"
        f"Total rejoin pending: <b>{count}</b>\n"
        "Pilih request yang ingin dibuka:"
    )
    await _edit_query_text(query, text, reply_markup=_rejoin_inbox_keyboard())


def _format_package_lines(package_names):
    pkg_text_lines = []
    for name in package_names:
        pkg_text_lines.append(f"• {html.escape(name)}")
    return "\n".join(pkg_text_lines) if pkg_text_lines else "Belum dipilih"


def _format_rejoin_detail(user_id, data):
    _, user_package_names = _get_rejoin_package_summary(data, "selected_pkgs")
    _, owner_package_names = _get_rejoin_package_summary(data, "owner_selected_pkgs")
    user_pkg_text = _format_package_lines(user_package_names)
    owner_pkg_text = _format_package_lines(owner_package_names)

    username = data.get("username", "-") if isinstance(data, dict) else "-"
    username_text = f"@{username}" if username and username != "-" else "Tidak ada"
    first_name = data.get("first_name", "User") if isinstance(data, dict) else "User"

    return (
        "🔄 <b>PERMINTAAN REJOIN</b>\n\n"
        f"👤 Nama: {html.escape(str(first_name))}\n"
        f"📌 Username: {html.escape(str(username_text))}\n"
        f"🆔 ID: <code>{user_id}</code>\n\n"
        f"📦 <b>Paket pilihan user:</b>\n{user_pkg_text}\n\n"
        f"✅ <b>Paket verifikasi owner:</b>\n{owner_pkg_text}\n\n"
        "📸 Bukti approve di atas."
    )


def _get_rejoin_package_summary(data, key="selected_pkgs"):
    selected_pkg_ids = data.get(key, []) if isinstance(data, dict) else []
    selected_pkg_ids = selected_pkg_ids or []
    packages_map = {p["id"]: p for p in get_all_packages()}
    normalized_ids = []
    package_names = []
    for pid in selected_pkg_ids:
        try:
            pkg_id = int(pid)
        except (TypeError, ValueError):
            continue
        normalized_ids.append(pkg_id)
        pkg = packages_map.get(pkg_id)
        if pkg:
            package_names.append(str(pkg["nama"]))
    return normalized_ids, package_names


def _save_rejoin_transaction(user_id, data, status, package_ids=None):
    data_for_summary = dict(data) if isinstance(data, dict) else {}
    if package_ids is not None:
        data_for_summary["selected_pkgs"] = package_ids

    selected_ids, package_names = _get_rejoin_package_summary(data_for_summary)
    package_label = "Rejoin"
    if package_names:
        package_label = "Rejoin - " + ", ".join(package_names)

    transaction_code = _generate_transaction_code({
        "paket_type": "rejoin",
        "selected_ids": selected_ids,
    }, user_id)
    save_transaction(
        user_id=user_id,
        package_type="rejoin",
        package_label=package_label,
        harga_bayar=0,
        promo_used=None,
        status=status,
        transaction_code=transaction_code
    )


def _rejoin_detail_keyboard(user_id, data):
    selected_owner_ids = set()
    if isinstance(data, dict):
        for pkg_id in data.get("owner_selected_pkgs", []) or []:
            try:
                selected_owner_ids.add(int(pkg_id))
            except (TypeError, ValueError):
                continue

    keyboard = []
    for pkg in get_all_packages():
        try:
            pkg_id = int(pkg["id"])
        except (TypeError, ValueError):
            continue
        mark = "✅" if pkg_id in selected_owner_ids else "⬜"
        keyboard.append([
            InlineKeyboardButton(
                f"{mark} {pkg['nama']}",
                callback_data=f"rejoin_verify_pkg_{user_id}_{pkg_id}"
            )
        ])

    keyboard.append([
        InlineKeyboardButton("✅ Approve", callback_data=f"rejoin_approve_{user_id}"),
        InlineKeyboardButton("❌ Tolak", callback_data=f"rejoin_cancel_{user_id}")
    ])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="rejoin_inbox")])
    return InlineKeyboardMarkup(keyboard)


def _processed_rejoin_keyboard():
    return InlineKeyboardMarkup([[
        InlineKeyboardButton("⬅️ Kembali", callback_data="rejoin_inbox")
    ]])


def _processed_vs_keyboard():
    return InlineKeyboardMarkup([[
        InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")
    ]])


async def _show_rejoin_detail(query, context, user_id):
    OPENED_REJOIN_IDS.add(user_id)
    data = REJOIN_PENDING.get(user_id)
    if not data:
        await query.answer("⚠️ Data rejoin tidak ditemukan.", show_alert=True)
        await _show_rejoin_inbox(query)
        return

    caption = _format_rejoin_detail(user_id, data)
    keyboard = _rejoin_detail_keyboard(user_id, data)
    photo_id = data.get("photo_id")
    if photo_id:
        await context.bot.send_photo(
            chat_id=query.message.chat_id if query.message else query.from_user.id,
            photo=photo_id,
            caption=caption,
            parse_mode="HTML",
            reply_markup=keyboard
        )
        return

    await _edit_query_text(query, caption, reply_markup=keyboard)


def _notification_label():
    total = get_open_contact_count() + get_pending_count() + len(REJOIN_PENDING)
    return f"🔔 Notifications ({total})" if total else "🔔 Notifications"


def _member_profile_keyboard():
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("⬅️ Kembali ke Menu", callback_data="menu")]
    ])


def _get_member_vip_entries(user_id):
    entries = []
    seen = set()

    def add_entry(name, link):
        name = str(name or "VIP").strip() or "VIP"
        link = str(link or "-").strip() or "-"
        key = (name.casefold(), link)
        if key in seen:
            return
        seen.add(key)
        entries.append({"name": name, "link": link})

    for item in get_links_by_user_id(user_id):
        if item.get("status") != "Aktif":
            continue
        add_entry(item.get("group_name"), item.get("link"))

    packages = get_all_packages()
    transactions = get_user_approved_vip_transactions(user_id)
    for tx in transactions:
        package_type = tx.get("package_type")
        package_label = str(tx.get("package_label") or "")
        if package_type == "take_all":
            matched_packages = packages
        else:
            package_label_lower = package_label.casefold()
            matched_packages = [
                pkg for pkg in packages
                if str(pkg.get("nama") or "").strip()
                and str(pkg.get("nama") or "").casefold() in package_label_lower
            ]

        for pkg in matched_packages:
            if pkg.get("link"):
                add_entry(pkg.get("nama"), pkg.get("link"))

    return entries


def _format_member_profile_text(user, saved_user, total_payment, vip_entries):
    name = getattr(user, "full_name", None) or user.first_name or "-"
    username = f"@{user.username}" if user.username else "-"
    join_at = (saved_user or {}).get("join_date") or "-"

    package_lines = []
    max_entries = 30
    for item in vip_entries[:max_entries]:
        package_lines.append(
            f"- {html.escape(item['name'])} (LINK : {html.escape(item['link'])})"
        )
    if len(vip_entries) > max_entries:
        package_lines.append(f"- ... dan {len(vip_entries) - max_entries} paket lainnya")
    if not package_lines:
        package_lines.append("- Belum ada paket VIP aktif")

    package_text = "\n".join(package_lines)
    return (
        "👤 <b>DETAIL PROFIL MEMBER VIP VIRALINMAS :</b>\n"
        f"NAMA : {html.escape(name)}\n"
        f"USERNAME : {html.escape(username)}\n"
        f"USER ID : <code>{user.id}</code>\n"
        f"JOIN AT : {html.escape(str(join_at))}\n"
        f"TOTAL PAYMENT : <b>{_format_rupiah(total_payment)}</b>\n\n"
        "📦 <b>DETAIL PAKET VIP :</b>\n"
        f"{package_text}\n\n"
        f"{WARNING_ICON} <b><i>JANGAN MASUK LEWAT LINK KALO SUDAH KLIK LINK VIP</i></b> {WARNING_ICON}\n"
        f"{WARNING_ICON} <b><i>KALO KADALUARSA BERARTI SUDAH JOIN GRUP</i></b> {WARNING_ICON}\n"
        f"{WARNING_ICON} <b><i>CARI NAMA VIP NYA, TERUS PIN GRUP VIPNYA</i></b> {WARNING_ICON}\n"
        f"{WARNING_ICON} <b><i>SILAHKAN TERUSKAN PESAN LINK INI KE CATATAN PRIBADI</i></b> {WARNING_ICON}"
    )


async def _show_member_profile(query, context):
    user = query.from_user
    save_user(user.id, first_name=user.first_name, username=user.username)
    saved_user = get_user_by_id(user.id)
    total_payment = get_user_total_approved_payment(user.id)
    vip_entries = _get_member_vip_entries(user.id)
    text = _format_member_profile_text(user, saved_user, total_payment, vip_entries)
    await _edit_query_caption_or_text(query, text, reply_markup=_member_profile_keyboard())


def _owner_transaction_report_keyboard(back_callback="owner_group_info"):
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("📅 Laporan Hari Ini", callback_data="owner_report_today")],
        [InlineKeyboardButton("📆 Laporan Bulanan", callback_data="owner_report_month")],
        [InlineKeyboardButton("📦 Transaksi Terakhir", callback_data="owner_report_recent")],
        [InlineKeyboardButton("🔎 Cari User", callback_data="owner_report_search")],
        [InlineKeyboardButton("📤 Export CSV", callback_data="owner_report_export")],
        [InlineKeyboardButton("⬅️ Kembali", callback_data=back_callback)],
    ])


def _owner_transaction_detail_keyboard():
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("📊 Laporan", callback_data="owner_transaction_report")],
        [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_info")],
    ])


def _notification_category_label(context, label, count, key):
    if count <= 0:
        return f"✅ {label} (0)"

    seen = bool(context.user_data.get(f"notif_{key}_seen", False))
    if seen:
        return f"🟡 {label} ({count})"
    return f"🔴 {label} ({count})"


async def _show_notifications_menu(query, context):
    inbox_count = get_open_contact_count()
    order_count = get_pending_count()
    rejoin_count = len(REJOIN_PENDING)
    inbox_label = _notification_category_label(context, "📩 Pesan Masuk", inbox_count, "contact")
    order_label = _notification_category_label(context, "📦 Order Masuk", order_count, "order")
    rejoin_label = _notification_category_label(context, "🔄 Rejoin Requests", rejoin_count, "rejoin")

    await _edit_query_text(
        query,
        "🔔 <b>Notifications</b>\n\nPilih kategori notifikasi:",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton(order_label, callback_data="owner_order_inbox")],
            [InlineKeyboardButton(rejoin_label, callback_data="rejoin_inbox")],
            [InlineKeyboardButton(inbox_label, callback_data="owner_contact_inbox")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")]
        ])
    )


def _build_payment_selection_text(caption_order):
    lines = caption_order.splitlines()
    package_info = None
    body = caption_order.strip()

    if len(lines) >= 2 and lines[1].strip().startswith("💰"):
        package_info = "\n".join(lines[:2]).strip()
        body = "\n".join(lines[2:]).strip()
    elif caption_order.strip().startswith("📝 <b>Pesananmu:"):
        package_info = caption_order.strip()
        body = ""

    parts = []
    if body:
        parts.append(body)
    parts.append("💳 <b>Pilih metode pembayaran:</b>")
    if package_info:
        parts.append(package_info)
    return "\n\n".join(parts)


async def _show_payment_selection(query, context, caption_order, banner_file_id=None):
    """Tampilkan pilihan metode pembayaran ke user."""
    info = get_payment_info()
    has_qris = bool(info.get("qris_file_id") or os.path.exists(QRIS_PATH))
    has_dana = bool(info.get("dana_number"))

    keyboard = []
    if has_qris:
        keyboard.append([InlineKeyboardButton(
            "QRIS",
            callback_data="pay_method_qris",
            **premium_button_icon_kwargs(QRIS_BUTTON_ICON_ID)
        )])
    if has_dana:
        keyboard.append([InlineKeyboardButton(
            "Dana",
            callback_data="pay_method_dana",
            **premium_button_icon_kwargs(DANA_BUTTON_ICON_ID)
        )])
    keyboard.append([InlineKeyboardButton("❌ Batalkan", callback_data="batal_transaksi")])

    context.user_data["payment_caption"] = caption_order
    teks = _build_payment_selection_text(caption_order)
    markup = InlineKeyboardMarkup(keyboard)

    if visible_text_length(teks) > TELEGRAM_CAPTION_LIMIT:
        await _replace_query_with_text(query, context, teks, reply_markup=markup)
        return

    if banner_file_id:
        try:
            await query.edit_message_media(
                media=InputMediaPhoto(
                    media=banner_file_id,
                    caption=teks,
                    parse_mode="HTML"
                ),
                reply_markup=markup
            )
            return
        except Exception:
            try:
                await context.bot.send_photo(
                    chat_id=query.from_user.id,
                    photo=banner_file_id,
                    caption=teks,
                    parse_mode="HTML",
                    reply_markup=markup
                )
                return
            except Exception:
                pass

    try:
        await query.edit_message_caption(
            caption=teks,
            parse_mode="HTML",
            reply_markup=markup
        )
    except Exception:
        await query.edit_message_text(
            teks,
            parse_mode="HTML",
            reply_markup=markup
        )


def _build_take_all_promo_prompt(context):
    total = context.user_data.get("vip_take_all_total", 0)
    diskon = context.user_data.get("vip_take_all_discount", 0)
    subtotal = context.user_data.get("vip_take_all_subtotal", context.user_data.get("vip_harga_bayar", 0))

    text = (
        "🚀 <b>TAKE ALL</b>\n\n"
        f"Total Normal: <b>{_format_rupiah(total)}</b>\n"
        f"Diskon Take All 20%: <b>-{_format_rupiah(diskon)}</b>\n"
        f"Subtotal: <b>{_format_rupiah(subtotal)}</b>\n\n"
        "Punya kode promo?"
    )
    keyboard = InlineKeyboardMarkup([
        [InlineKeyboardButton("🎟️ Masukkan Kode Promo", callback_data="take_all_enter_promo")],
        [InlineKeyboardButton("Lewati Promo", callback_data="take_all_skip_promo")],
        [InlineKeyboardButton("❌ Batalkan", callback_data="batal_transaksi")],
    ])
    return text, keyboard


async def _show_take_all_promo_prompt(query, context):
    text, keyboard = _build_take_all_promo_prompt(context)
    await _edit_query_text(query, text, reply_markup=keyboard)


def _build_take_all_payment_caption(context):
    total = context.user_data.get("vip_take_all_total", 0)
    diskon = context.user_data.get("vip_take_all_discount", 0)
    subtotal = context.user_data.get("vip_take_all_subtotal", context.user_data.get("vip_harga_bayar", 0))

    return (
        "🚀 <b>TAKE ALL</b>\n\n"
        f"Total Normal: {_format_rupiah(total)}\n"
        f"Diskon Take All 20%: -{_format_rupiah(diskon)}\n"
        f"💰 <b>Total Bayar: {_format_rupiah(subtotal)}</b>"
    )


def _build_vip_multi_selection_view(packages, selected_ids):
    selected_set = set(selected_ids)
    selected_packages = [pkg for pkg in packages if pkg["id"] in selected_set]
    total = sum(pkg["harga"] for pkg in selected_packages)
    diskon = int(total * 0.10) if len(selected_packages) >= 2 else 0
    bayar = total - diskon

    text = "🎯 <b>Pilih Paket VIP</b>\n\n"
    keyboard = []
    for pkg in packages:
        harga_fmt = _format_rupiah(pkg["harga"])
        mark = "✅" if pkg["id"] in selected_set else "⬜"
        text += f"{mark} {html.escape(pkg['nama'])} - {harga_fmt}\n"
        keyboard.append([
            InlineKeyboardButton(
                f"{mark} {pkg['nama']} - {harga_fmt}",
                callback_data=f"vip_multi_toggle_{pkg['id']}"
            )
        ])

    text += (
        f"\nTotal: <b>{_format_rupiah(total)}</b>\n"
        "Diskon: <b>10%</b>"
    )
    if len(selected_packages) >= 2:
        text += (
            f"\nPotongan: <b>-{_format_rupiah(diskon)}</b>\n"
            f"Total Bayar: <b>{_format_rupiah(bayar)}</b>"
        )
    else:
        text += "\n\nPilih minimal 2 paket untuk lanjut bayar."

    keyboard.append([InlineKeyboardButton("✅ Lanjut Bayar", callback_data="vip_multi_continue")])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="vip_take_menu")])
    return text, InlineKeyboardMarkup(keyboard)


async def _show_vip_multi_selection(query, context, packages, selected_ids):
    text, markup = _build_vip_multi_selection_view(packages, selected_ids)
    if query.message and (query.message.photo or query.message.video) and visible_text_length(text) > TELEGRAM_CAPTION_LIMIT:
        await _replace_query_with_text(
            query,
            context,
            text,
            reply_markup=markup
        )
        return

    try:
        await query.edit_message_caption(
            caption=text,
            parse_mode="HTML",
            reply_markup=markup
        )
    except Exception:
        await query.edit_message_text(
            text,
            parse_mode="HTML",
            reply_markup=markup
        )


def _set_transaction_back(context, view, **data):
    context.user_data["transaction_back_view"] = view
    context.user_data.pop("transaction_back_admin_id", None)
    context.user_data.pop("transaction_back_selected_ids", None)
    for key, value in data.items():
        context.user_data[f"transaction_back_{key}"] = value


def _get_transaction_back(context):
    selected_ids = context.user_data.get("transaction_back_selected_ids") or []
    return {
        "view": context.user_data.get("transaction_back_view"),
        "admin_id": context.user_data.get("transaction_back_admin_id"),
        "selected_ids": list(selected_ids),
    }


async def _delete_transaction_extra_messages(query, context, message_ids):
    current_message_id = query.message.message_id if query.message else None
    for message_id in set(message_ids):
        if not message_id or message_id == current_message_id:
            continue
        try:
            await context.bot.delete_message(
                chat_id=query.from_user.id,
                message_id=message_id
            )
        except Exception:
            pass


async def _show_vip_package_list(query, context):
    packages = get_visible_packages()
    keyboard = []
    for pkg in packages:
        harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
        keyboard.append([InlineKeyboardButton(
            f"{pkg['nama']} - Rp {harga_fmt}",
            callback_data=f"vip_pkg_{pkg['id']}"
        )])

    if packages:
        keyboard.append([InlineKeyboardButton("📦 Menu Take", callback_data="vip_take_menu")])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="menu")])
    markup = InlineKeyboardMarkup(keyboard)
    caption = "🛍️ Pilih paket VIP:" if packages else "⚠️ Belum ada paket VIP yang tersedia saat ini."

    from services.payment_info_service import get_banner_file_id
    banner = get_banner_file_id() or (open(BANNER_PATH, "rb") if os.path.exists(BANNER_PATH) else None)
    if banner and query.message and (query.message.photo or query.message.video):
        try:
            await query.edit_message_media(
                media=InputMediaPhoto(banner, caption=caption),
                reply_markup=markup
            )
            return
        except Exception:
            pass

    if banner and query.message:
        try:
            await query.message.reply_photo(
                photo=banner,
                caption=caption,
                reply_markup=markup
            )
            try:
                await query.message.delete()
            except Exception:
                pass
            return
        except Exception:
            pass

    await _edit_query_text(query, caption, reply_markup=markup)


async def _show_vip_take_menu(query):
    packages = get_visible_packages()
    if not packages:
        await query.answer("Belum ada paket VIP yang tersedia saat ini.", show_alert=True)
        return

    keyboard = [
        [InlineKeyboardButton("🎯 Pilih Beberapa", callback_data="vip_take_choose")],
        [InlineKeyboardButton("🚀 Take All", callback_data="vip_take_all")],
        [InlineKeyboardButton("⬅️ Kembali", callback_data="daftar")]
    ]
    await _edit_query_caption_or_text(
        query,
        "📦 <b>Menu Take</b>\n\nPilih opsi pembelian paket:",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


async def _show_vs_admin_catalog(query, context, admin_id):
    config = get_vs(admin_id)
    if not config:
        await query.answer("VS tidak ditemukan", show_alert=True)
        return False

    keyboard = []
    for item in config["prices"]:
        keyboard.append([
            InlineKeyboardButton(
                f"{item['nama']} - Rp {item['harga']}",
                callback_data=f"vs_order_{admin_id}_{item['nama']}"
            )
        ])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="vs_menu")])

    image_id = config.get("image", "").strip()
    vs_caption = config.get("caption") or "Belum ada katalog VCS"
    vs_parse_mode = parse_mode_for_custom_emoji_html(vs_caption)
    vs_markup = InlineKeyboardMarkup(keyboard)

    if visible_text_length(vs_caption) > TELEGRAM_CAPTION_LIMIT:
        await _replace_query_with_text(
            query,
            context,
            vs_caption,
            reply_markup=vs_markup,
            parse_mode=vs_parse_mode
        )
        return True

    if image_id and query.message and (query.message.photo or query.message.video):
        try:
            await query.edit_message_media(
                media=InputMediaPhoto(
                    media=image_id,
                    caption=vs_caption,
                    parse_mode=vs_parse_mode
                ),
                reply_markup=vs_markup
            )
            return True
        except Exception:
            pass

    if image_id and query.message:
        try:
            await query.message.reply_photo(
                photo=image_id,
                caption=vs_caption,
                parse_mode=vs_parse_mode,
                reply_markup=vs_markup
            )
            try:
                await query.message.delete()
            except Exception:
                pass
            return True
        except Exception:
            pass

    await _edit_query_caption_or_text(
        query,
        vs_caption,
        reply_markup=vs_markup,
        parse_mode=vs_parse_mode
    )
    return True


async def _show_transaction_back_panel(query, context, back):
    view = back.get("view")
    if view == "daftar":
        await _show_vip_package_list(query, context)
        return

    if view == "vip_take_menu":
        await _show_vip_take_menu(query)
        return

    if view == "vip_multi_selection":
        selected_ids = back.get("selected_ids") or []
        packages = get_visible_packages()
        context.user_data["vip_multi_selected_ids"] = selected_ids
        await _show_vip_multi_selection(query, context, packages, selected_ids)
        return

    if view == "vs_admin" and back.get("admin_id"):
        try:
            admin_id = int(back["admin_id"])
        except (TypeError, ValueError):
            admin_id = None
        if admin_id and await _show_vs_admin_catalog(query, context, admin_id):
            return

    await show_main_menu(query, query.from_user)


def _package_visibility_status(pkg):
    return "🟢 ON" if is_package_visible(pkg) else "🔴 OFF"


async def _show_owner_vip_menu(query, context, clear_state=False):
    if clear_state:
        for key in [
            "waiting_vip_add",
            "waiting_vip_add_name",
            "waiting_vip_add_price",
            "waiting_vip_add_group",
            "waiting_vip_add_link",
            "vip_add_draft",
            "waiting_vip_edit_name",
            "waiting_vip_edit_harga",
            "waiting_vip_edit_group",
            "waiting_vip_edit_banner",
            "waiting_vip_edit_caption",
            "editing_vip_id",
            "vip_add_flow",
        ]:
            context.user_data.pop(key, None)

    packages = get_all_packages()
    teks = "🛍️ <b>KELOLA PAKET VIP</b>\n\n"

    if not packages:
        teks += "Belum ada paket VIP.\n"
    else:
        teks += "Klik status paket untuk ON/OFF tampilan user.\n\n"
        for pkg in packages:
            status = _package_visibility_status(pkg)
            harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
            teks += f"{status} ID <code>{pkg['id']}</code> ─ {html.escape(pkg['nama'])} ─ Rp {harga_fmt}\n"
            if pkg.get("group_id"):
                teks += f"   Group: <code>{pkg['group_id']}</code>\n"
            if pkg.get("link"):
                teks += f"   Link: {html.escape(pkg['link'])}\n"

    keyboard = []
    for pkg in packages:
        status = _package_visibility_status(pkg)
        keyboard.append([
            InlineKeyboardButton(
                f"{status} {pkg['nama']}",
                callback_data=f"owner_vip_toggle_visible_{pkg['id']}"
            )
        ])

    keyboard.extend([
        [InlineKeyboardButton("➕ Tambah Paket", callback_data="owner_vip_add")],
        [InlineKeyboardButton("✏️ Edit Paket", callback_data="owner_vip_edit_select")],
        [InlineKeyboardButton("🗑️ Hapus Paket", callback_data="owner_vip_delete")],
        [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")]
    ])

    await query.edit_message_text(
        teks,
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


def _caption_preview_text(caption):
    if not caption:
        return "Menggunakan caption default bawaan bot."
    preview = html.escape(caption)
    if len(preview) > 2600:
        preview = preview[:2600] + "\n...terpotong"
    return f"<pre>{preview}</pre>"


def _format_vip_caption(pkg, harga_fmt):
    caption = pkg.get("caption")
    if not caption:
        return f"📝 <b>Pesananmu: {html.escape(pkg['nama'])} - Rp {harga_fmt}</b>"

    return (
        caption
        .replace("{nama}", html.escape(str(pkg.get("nama", ""))))
        .replace("{harga}", f"Rp {harga_fmt}")
        .replace("{harga_angka}", str(pkg.get("harga", "")))
    )


async def _show_caption_menu(query):
    caption = get_main_caption()
    status = "✅ Custom" if caption else "Default"
    keyboard = [
        [InlineKeyboardButton("👁 Preview Sebagai User", callback_data="owner_caption_preview")],
        [InlineKeyboardButton("✏️ Ubah Caption", callback_data="owner_set_caption")]
    ]
    if caption:
        keyboard.append([InlineKeyboardButton("🔄 Reset Default", callback_data="owner_reset_caption")])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_settings")])

    await _edit_query_text(
        query,
        "📝 <b>Kelola Caption Menu Utama</b>\n\n"
        f"Status: <b>{status}</b>\n\n"
        "Placeholder:\n"
        "<code>{first_name}</code>, <code>{username}</code>, <code>{user_id}</code>\n\n"
        "Caption saat ini:\n"
        f"{_caption_preview_text(caption)}",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


async def _show_main_caption_preview(query):
    preview = get_main_menu_text(query.from_user)
    await _edit_query_text(
        query,
        "👁 <b>Preview Caption Menu Utama</b>\n\n"
        f"{preview}",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("✏️ Edit Caption", callback_data="owner_set_caption")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_caption_menu")]
        ])
    )


async def _show_vip_caption_preview(query, pkg_id):
    pkg = get_package_by_id(pkg_id)
    if not pkg:
        await query.answer("Paket tidak ditemukan.", show_alert=True)
        return

    harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
    preview = _format_vip_caption(pkg, harga_fmt)
    keyboard = [
        [InlineKeyboardButton("✏️ Edit Caption", callback_data=f"owner_vip_edit_caption_{pkg_id}")]
    ]
    if pkg.get("caption"):
        keyboard.append([InlineKeyboardButton("🗑 Reset Caption", callback_data=f"owner_vip_reset_caption_{pkg_id}")])
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data=f"owner_vip_edit_detail_{pkg_id}")])

    await _edit_query_text(
        query,
        f"📝 <b>Caption Paket {html.escape(pkg['nama'])}</b>\n\n"
        "Preview:\n\n"
        f"{preview}",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )


async def _show_vip_edit_detail(query, pkg_id):
    pkg = get_package_by_id(pkg_id)
    if not pkg:
        await query.answer("Paket tidak ditemukan.", show_alert=True)
        return False

    harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
    teks = (
        f"✏️ <b>EDIT PAKET VIP #{pkg['id']}</b>\n\n"
        f"Nama    : <b>{html.escape(pkg['nama'])}</b>\n"
        f"Harga   : Rp {harga_fmt}\n"
        f"Tampil  : {_package_visibility_status(pkg)}\n"
    )
    if pkg.get("group_id"):
        teks += f"Group ID: <code>{pkg['group_id']}</code>\n"
    if pkg.get("link"):
        teks += f"Link    : {html.escape(pkg['link'])}\n"
    else:
        teks += "Link    : -\n"
    teks += f"Banner  : {'✅ Sudah diset' if pkg.get('banner_file_id') else '❌ Belum diset'}\n"
    if pkg.get("caption"):
        caption_preview = html.escape(pkg["caption"])
        if len(caption_preview) > 400:
            caption_preview = caption_preview[:400] + "\n...terpotong"
        teks += f"Caption :\n<pre>{caption_preview}</pre>\n"
    else:
        teks += "Caption : Default\n"

    keyboard = [
        [InlineKeyboardButton("✏️ Edit Nama", callback_data=f"owner_vip_edit_name_{pkg['id']}")],
        [InlineKeyboardButton("💰 Edit Harga", callback_data=f"owner_vip_edit_harga_{pkg['id']}")],
        [InlineKeyboardButton("🔗 Edit Group ID/Link", callback_data=f"owner_vip_edit_group_{pkg['id']}")],
        [InlineKeyboardButton("🖼 Edit Banner", callback_data=f"owner_vip_edit_banner_{pkg['id']}")],
        [InlineKeyboardButton("👁 Preview Caption", callback_data=f"owner_vip_preview_caption_{pkg['id']}")],
        [InlineKeyboardButton("📝 Edit Caption", callback_data=f"owner_vip_edit_caption_{pkg['id']}")],
        [InlineKeyboardButton(
            "🔴 OFF Tampilan User" if is_package_visible(pkg) else "🟢 ON Tampilan User",
            callback_data=f"owner_vip_toggle_visible_detail_{pkg['id']}"
        )],
    ]
    if pkg.get("banner_file_id") or pkg.get("caption"):
        reset_buttons = []
        if pkg.get("banner_file_id"):
            reset_buttons.append(InlineKeyboardButton("🗑 Reset Banner", callback_data=f"owner_vip_reset_banner_{pkg['id']}"))
        if pkg.get("caption"):
            reset_buttons.append(InlineKeyboardButton("🗑 Reset Caption", callback_data=f"owner_vip_reset_caption_{pkg['id']}"))
        keyboard.append(reset_buttons)
    keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")])

    await query.edit_message_text(
        teks,
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup(keyboard)
    )
    return True


async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    try:
        await query.answer()
    except Exception:
        pass

    # =========================
    # GATE MAINTENANCE
    # =========================
    if get_maintenance() and not is_owner(query.from_user.id) and query.data != "owner_toggle_maintenance":
        await query.answer(
            "🔧 Bot sedang maintenance, harap tunggu...",
            show_alert=True
        )
        return

    if (
        query.data == "user_profile"
        or query.data == "user_transactions"
        or query.data.startswith("user_transactions_")
    ):
        await _show_member_profile(query, context)
        return

    if query.data == "contact_admin":
        context.user_data.pop("waiting_contact_message", None)
        context.user_data.pop("contact_topic", None)

        keyboard = InlineKeyboardMarkup([
            [
                InlineKeyboardButton("Pembayaran", callback_data="contact_topic_payment"),
                InlineKeyboardButton("Akses Grup", callback_data="contact_topic_access")
            ],
            [InlineKeyboardButton("Lainnya", callback_data="contact_topic_other")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="menu")]
        ])
        await _edit_query_caption_or_text(
            query,
            "💬 <b>Contact Admin</b>\n\nPilih topik bantuan:",
            reply_markup=keyboard
        )
        return

    if query.data.startswith("contact_topic_"):
        topic_key = query.data.replace("contact_topic_", "")
        topic = CONTACT_TOPICS.get(topic_key)
        if not topic:
            await query.answer("Topik tidak tersedia.", show_alert=True)
            return

        context.user_data["waiting_contact_message"] = True
        context.user_data["contact_topic"] = topic

        await _edit_query_caption_or_text(
            query,
            f"🏷️ Topik: <b>{topic}</b>\n\nSilakan ketik pesan kamu untuk admin.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="menu")]
            ])
        )
        return

    if query.data == "owner_contact_inbox":
        if not is_owner(query.from_user.id):
            return

        context.user_data["notif_contact_seen"] = True
        context.user_data.pop("waiting_contact_reply", None)
        await _show_contact_inbox(query)
        return

    if query.data.startswith("owner_contact_open_"):
        if not is_owner(query.from_user.id):
            return

        context.user_data.pop("waiting_contact_reply", None)
        message_id = int(query.data.replace("owner_contact_open_", ""))
        OPENED_CONTACT_IDS.add(message_id)
        await _show_contact_detail(query, message_id)
        return

    if query.data.startswith("owner_contact_reply_"):
        if not is_owner(query.from_user.id):
            return

        message_id = int(query.data.replace("owner_contact_reply_", ""))
        item = get_contact_message(message_id)
        if not item:
            await query.answer("Pesan tidak ditemukan.", show_alert=True)
            await _show_contact_inbox(query)
            return

        context.user_data["waiting_contact_reply"] = message_id
        await _edit_query_text(
            query,
            "💬 <b>Balas Pesan</b>\n\nKetik balasan untuk user.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data=f"owner_contact_open_{message_id}")]
            ])
        )
        return

    if query.data.startswith("owner_contact_quick_"):
        if not is_owner(query.from_user.id):
            return

        message_id = int(query.data.replace("owner_contact_quick_", ""))
        item = get_contact_message(message_id)
        if not item:
            await query.answer("Pesan tidak ditemukan.", show_alert=True)
            await _show_contact_inbox(query)
            return

        keyboard = []
        for key, reply_text in CONTACT_QUICK_REPLIES.items():
            keyboard.append([
                InlineKeyboardButton(
                    _short_text(reply_text, 48),
                    callback_data=f"owner_contact_send_quick_{message_id}_{key}"
                )
            ])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data=f"owner_contact_open_{message_id}")])
        await _edit_query_text(
            query,
            "⚡ <b>Quick Reply</b>\n\nPilih balasan cepat untuk user:",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )
        return

    if query.data.startswith("owner_contact_send_quick_"):
        if not is_owner(query.from_user.id):
            return

        payload = query.data.replace("owner_contact_send_quick_", "", 1)
        message_id_str, reply_key = payload.split("_", 1)
        message_id = int(message_id_str)
        item = get_contact_message(message_id)
        reply_text = CONTACT_QUICK_REPLIES.get(reply_key)
        if not item or not reply_text:
            await query.answer("Quick reply tidak tersedia.", show_alert=True)
            await _show_contact_inbox(query)
            return

        await context.bot.send_message(
            chat_id=item["user_id"],
            text=f"📩 <b>Balasan dari Admin:</b>\n\n{html.escape(reply_text)}",
            parse_mode="HTML"
        )
        mark_contact_replied(message_id)
        await query.answer("✅ Quick reply terkirim.", show_alert=True)
        await _show_contact_detail(query, message_id)
        return

    if query.data.startswith("owner_contact_close_"):
        if not is_owner(query.from_user.id):
            return

        context.user_data.pop("waiting_contact_reply", None)
        message_id = int(query.data.replace("owner_contact_close_", ""))
        item = get_contact_message(message_id)
        if not item:
            await query.answer("Pesan tidak ditemukan.", show_alert=True)
            await _show_contact_inbox(query)
            return

        close_contact_message(message_id)
        try:
            await context.bot.send_message(
                chat_id=item["user_id"],
                text="✅ <b>Tiket bantuan kamu sudah ditutup.</b>\n\nJika masih ada kendala, silakan buka Bantuan Admin lagi.",
                parse_mode="HTML"
            )
        except Exception:
            pass
        await query.answer("✅ Tiket ditutup.", show_alert=True)
        await _show_contact_detail(query, message_id)
        return

    if query.data.startswith("owner_contact_delete_"):
        if not is_owner(query.from_user.id):
            return

        context.user_data.pop("waiting_contact_reply", None)
        message_id = int(query.data.replace("owner_contact_delete_", ""))
        delete_contact_message(message_id)
        await query.answer("Pesan dihapus.", show_alert=True)
        await _show_contact_inbox(query)
        return

    if query.data == "owner_notifications":
        if not is_owner(query.from_user.id):
            return

        await _show_notifications_menu(query, context)
        return

    if query.data == "owner_order_inbox":
        if not is_owner(query.from_user.id):
            return

        context.user_data["notif_order_seen"] = True
        await _show_order_inbox(query)
        return

    if query.data == "rejoin_inbox":
        if not is_owner(query.from_user.id):
            return

        context.user_data["notif_rejoin_seen"] = True
        await _show_rejoin_inbox(query)
        return

    if query.data.startswith("owner_order_open_"):
        if not is_owner(query.from_user.id):
            return

        user_id = int(query.data.replace("owner_order_open_", ""))
        OPENED_ORDER_IDS.add(user_id)
        await _show_order_detail(query, context, user_id)
        return

    # =========================
    # PANEL ADMIN / OWNER
    # =========================

    if query.data == "panel_admin":

        if not is_admin(query.from_user.id):
            return

        user = query.from_user
        admin_data = get_admin_by_id(user.id)

        nama_admin = (
            admin_data["nama"]
            if admin_data
            else user.first_name
        )

        username = (
            f"@{user.username}"
            if user.username
            else "Tidak disetel"
        )

        panel_title = (
            "OWNER PANEL"
            if is_owner(user.id)
            else f"ADMIN PANEL - {nama_admin}"
        )

        panel_icon = (
            "👑"
            if is_owner(user.id)
            else "🎖️"
        )

        if is_owner(user.id):
            keyboard = [
                [
                    InlineKeyboardButton(_notification_label(), callback_data="owner_notifications")
                ],
                [
                    InlineKeyboardButton("📣 Broadcast", callback_data="broadcast_menu")
                ],
                [
                    InlineKeyboardButton("📊 Info & Laporan", callback_data="owner_group_info")
                ],
                [
                    InlineKeyboardButton("📦 Kelola Product", callback_data="owner_group_product")
                ],
                [
                    InlineKeyboardButton("👮 Kelola Admin", callback_data="owner_group_admin")
                ],
                [
                    InlineKeyboardButton("⚙️ Pengaturan Bot", callback_data="owner_group_settings")
                ],
            ]
        else:
            vs_status = admin_data.get("vs_enabled", False) if admin_data else False
            toggle_label = "🔴 OFF kan Diri Saya" if vs_status else "🟢 ON kan Diri Saya"
            keyboard = [
                [
                    InlineKeyboardButton("🎥 Kelola Paket VCS", callback_data="vs_manage")
                ],
                [
                    InlineKeyboardButton(toggle_label, callback_data="admin_self_toggle")
                ]
            ]

        if is_owner(user.id):
            pass

        teks_admin = f"""{panel_icon} <b>{panel_title}</b>        
        👤 <b>Nama Admin:</b> {nama_admin}
        👤 <b>Nama:</b> {user.first_name}
        📌 <b>Username:</b> {username}
        🆔 <b>ID:</b> <code>{user.id}</code>

        🟢 Bot aktif dan siap digunakan.
        """

        await _edit_query_text(
            query,
            teks_admin,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    # =========================
    # TOGGLE MAINTENANCE
    # =========================

    elif query.data == "owner_toggle_maintenance":
        if not is_owner(query.from_user.id):
            return

        current = get_maintenance()
        new_state = not current
        set_maintenance(new_state)

        if new_state:
            notif = (
                "🔧 <b>Bot sedang dalam maintenance.</b>\n\n"
                "Mohon bersabar, bot akan segera aktif kembali. Terima kasih! 🙏"
            )
            status_text = "🔴 Maintenance <b>AKTIF</b>"
        else:
            notif = "✅ <b>Bot sudah kembali aktif!</b>\n\nTerima kasih sudah menunggu. 🙏"
            status_text = "🟢 Maintenance <b>NONAKTIF</b>"

        toggle_label = "🔴 Nonaktifkan Maintenance" if new_state else "🔧 Aktifkan Maintenance"
        await query.edit_message_text(
            f"✅ Status berhasil diubah!\n\n"
            f"{status_text}\n\n"
            f"📢 Mengirim notifikasi ke semua pengguna...",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton(toggle_label, callback_data="owner_toggle_maintenance")],
                [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")]
            ])
        )

        async def _broadcast_maintenance(bot, message, user_ids):
            for uid in user_ids:
                if is_owner(uid):
                    continue
                try:
                    await bot.send_message(chat_id=uid, text=message, parse_mode="HTML")
                    await asyncio.sleep(0.05)
                except Exception:
                    pass

        all_users = get_all_users()
        asyncio.create_task(_broadcast_maintenance(context.bot, notif, all_users))

    # =========================
    # BROADCAST
    # =========================

    elif (
        query.data.startswith("broadcast")
        or query.data.startswith("delete_template")
        or query.data == "skip_media"
        or query.data == "skip_button_text"
    ):

        await handle_broadcast_buttons(update, context)
        return
    elif query.data == "owner_group_info":
        if not is_owner(query.from_user.id):
            return

        context.user_data.pop("waiting_owner_report_search", None)
        keyboard = [
            [InlineKeyboardButton("📊 Laporan", callback_data="owner_transaction_report")],
            [InlineKeyboardButton("💸 Cek Promo", callback_data="admin_cekpromo")],
            [
                InlineKeyboardButton("👥 Info Grup", callback_data="admin_infogrup"),
                InlineKeyboardButton("📇 Cek User", callback_data="admin_cekuser")
            ],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")],
        ]

        await query.edit_message_text(
            "📊 <b>Info & Laporan</b>\n\nPilih menu:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_transaction_report":
        if not is_owner(query.from_user.id):
            return

        context.user_data.pop("waiting_owner_report_search", None)
        await _edit_query_text(
            query,
            format_owner_transaction_dashboard(),
            reply_markup=_owner_transaction_report_keyboard()
        )

    elif query.data in ["owner_report_today", "owner_report_month"]:
        if not is_owner(query.from_user.id):
            return

        period = "today" if query.data == "owner_report_today" else "month"
        await _edit_query_text(
            query,
            format_period_transaction_report(period),
            reply_markup=_owner_transaction_detail_keyboard()
        )

    elif query.data == "owner_report_recent":
        if not is_owner(query.from_user.id):
            return

        await _edit_query_text(
            query,
            format_recent_transaction_report(),
            reply_markup=_owner_transaction_detail_keyboard()
        )

    elif query.data == "owner_report_search":
        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_owner_report_search"] = True
        await _edit_query_text(
            query,
            "🔎 <b>Cari Transaksi User</b>\n\n"
            "Ketik User ID atau username.\n"
            "Contoh: <code>123456789</code> atau <code>@username</code>",
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("❌ Batal", callback_data="owner_transaction_report")
            ]])
        )

    elif query.data == "owner_report_export":
        if not is_owner(query.from_user.id):
            return

        csv_text = build_transaction_csv()
        csv_file = BytesIO(csv_text.encode("utf-8-sig"))
        filename = f"transactions-{datetime.now().strftime('%Y%m%d-%H%M%S')}.csv"
        csv_file.name = filename
        await context.bot.send_document(
            chat_id=query.from_user.id,
            document=csv_file,
            filename=filename,
            caption="📤 Export CSV transaksi."
        )

    elif query.data == "owner_group_product":
        if not is_owner(query.from_user.id):
            return

        keyboard = [
            [InlineKeyboardButton("🛍️ Kelola Paket VIP", callback_data="owner_vip_menu")],
            [InlineKeyboardButton("➕ Buat Promo", callback_data="owner_buatpromo")],
            [
                InlineKeyboardButton("🗑️ Hapus Link", callback_data="owner_hapuslink"),
                InlineKeyboardButton("✏️ Edit Link", callback_data="owner_editlink")
            ],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")],
        ]

        await query.edit_message_text(
            "📦 <b>Kelola Product</b>\n\nPilih menu:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_group_admin":
        if not is_owner(query.from_user.id):
            return

        keyboard = [
            [InlineKeyboardButton("🎥 Kelola VCS", callback_data="vs_manage")],
            [InlineKeyboardButton("👮 Manage Admin", callback_data="owner_adminmenu")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")],
        ]

        await query.edit_message_text(
            "👮 <b>Kelola Admin</b>\n\nPilih menu:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_group_settings":
        if not is_owner(query.from_user.id):
            return

        context.user_data.pop("waiting_main_caption", None)

        keyboard = [
            [InlineKeyboardButton("🖼 Ganti Banner", callback_data="owner_set_banner")],
            [InlineKeyboardButton("📝 Kelola Caption", callback_data="owner_caption_menu")],
            [InlineKeyboardButton("💳 Kelola Payment", callback_data="owner_payment_menu")],
            [InlineKeyboardButton("📢 Kelola Channel", callback_data="owner_channel_menu")],
            [InlineKeyboardButton("📋 Daftar User", callback_data="owner_user_list")],
            [InlineKeyboardButton("📦 Backup Database", callback_data="owner_backup_db")],
            [
                InlineKeyboardButton(
                    "🔴 Nonaktifkan Maintenance" if get_maintenance() else "🔧 Aktifkan Maintenance",
                    callback_data="owner_toggle_maintenance"
                )
            ],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")],
        ]

        await query.edit_message_text(
            "⚙️ <b>Pengaturan Bot</b>\n\nPilih menu pengaturan:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_user_list":
        if not is_owner(query.from_user.id):
            return

        cursor.execute("SELECT COUNT(*) FROM users")
        total_user_start = cursor.fetchone()[0]
        cursor.execute("SELECT COUNT(DISTINCT user_id) FROM transactions WHERE status = 'approved'")
        total_user_join = cursor.fetchone()[0]
        cursor.execute("SELECT COUNT(*) FROM transactions WHERE status = 'approved'")
        total_join_entries = cursor.fetchone()[0]
        cursor.execute("SELECT COUNT(DISTINCT user_id) FROM users WHERE user_id NOT IN (SELECT DISTINCT user_id FROM transactions)")
        total_user_start_only = cursor.fetchone()[0]

        keyboard = [
            [InlineKeyboardButton(f"👥 Start + Join ({total_user_join})", callback_data="owner_user_start_join")],
            [InlineKeyboardButton(f"📋 User Join Paket ({total_join_entries})", callback_data="owner_user_join_paket")],
            [InlineKeyboardButton(f"📦 User Join Per Paket", callback_data="owner_user_join_perpaket")],
            [InlineKeyboardButton(f"🆕 Start Saja ({total_user_start_only})", callback_data="owner_user_start_only")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_settings")],
        ]

        await query.edit_message_text(
            f"📋 <b>Daftar User</b>\n\n"
            f"Total user /start: <b>{total_user_start}</b>\n"
            f"Total user join paket (distinct): <b>{total_user_join}</b>\n"
            f"Total entri join paket: <b>{total_join_entries}</b>\n"
            f"Total user start saja: <b>{total_user_start_only}</b>\n\n"
            f"Pilih menu:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_user_start_join":
        if not is_owner(query.from_user.id):
            return
        cursor.execute(
            "SELECT u.user_id, u.first_name, u.username, u.join_date "
            "FROM users u "
            "INNER JOIN (SELECT DISTINCT user_id FROM transactions WHERE status = 'approved') t ON u.user_id = t.user_id "
            "ORDER BY u.join_date DESC"
        )
        rows = cursor.fetchall()
        if not rows:
            await query.answer("❌ Tidak ada user.", show_alert=True)
            return
        context.user_data["owner_user_list"] = {
            "rows": [dict(r) for r in rows],
            "page": 0,
            "type": "start_join"
        }
        await _show_user_list_page(query, context)

    elif query.data == "owner_user_join_paket":
        if not is_owner(query.from_user.id):
            return
        cursor.execute(
            "SELECT t.user_id, u.first_name, u.username, t.package_label, t.created_at "
            "FROM transactions t "
            "LEFT JOIN users u ON t.user_id = u.user_id "
            "WHERE t.status = 'approved' "
            "ORDER BY t.created_at DESC"
        )
        rows = cursor.fetchall()
        if not rows:
            await query.answer("❌ Tidak ada data.", show_alert=True)
            return
        context.user_data["owner_user_list"] = {
            "rows": [dict(r) for r in rows],
            "page": 0,
            "type": "join_paket"
        }
        await _show_user_list_page(query, context)

    elif query.data == "owner_user_join_perpaket":
        if not is_owner(query.from_user.id):
            return
        cursor.execute(
            "SELECT package_label, COUNT(DISTINCT user_id) as total "
            "FROM transactions WHERE status = 'approved' "
            "GROUP BY package_label ORDER BY total DESC"
        )
        rows = cursor.fetchall()
        if not rows:
            await query.answer("❌ Tidak ada data.", show_alert=True)
            return
        lines = [f"📦 <b>User Join Per Paket</b>\n"]
        for r in rows:
            label = html.escape(r["package_label"] or "-")
            lines.append(f"• {label}: <b>{r['total']} user</b>")
        text = "\n".join(lines)
        keyboard = [[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_user_list")]]
        await _edit_query_text(query, text, reply_markup=InlineKeyboardMarkup(keyboard))

    elif query.data == "owner_user_start_only":
        if not is_owner(query.from_user.id):
            return
        cursor.execute(
            "SELECT u.user_id, u.first_name, u.username, u.join_date "
            "FROM users u "
            "LEFT JOIN transactions t ON u.user_id = t.user_id "
            "WHERE t.user_id IS NULL "
            "ORDER BY u.join_date DESC"
        )
        rows = cursor.fetchall()
        if not rows:
            await query.answer("❌ Tidak ada user.", show_alert=True)
            return
        context.user_data["owner_user_list"] = {
            "rows": [dict(r) for r in rows],
            "page": 0,
            "type": "start_only"
        }
        await _show_user_list_page(query, context)

    elif query.data == "owner_user_list_page":
        if not is_owner(query.from_user.id):
            return
        await _show_user_list_page(query, context)

    elif query.data == "owner_backup_db":
        if not is_owner(query.from_user.id):
            return

        backup_path = None
        try:
            backup_path = create_database_backup()
            filename = os.path.basename(backup_path)
            with open(backup_path, "rb") as db_file:
                await context.bot.send_document(
                    chat_id=query.from_user.id,
                    document=db_file,
                    filename=filename,
                    caption="📦 Backup database bot. Simpan file ini di tempat aman."
                )
            await _edit_query_text(
                query,
                "✅ <b>Backup database berhasil dikirim.</b>",
                reply_markup=InlineKeyboardMarkup([[
                    InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_settings")
                ]])
            )
        except Exception as e:
            await _edit_query_text(
                query,
                f"⚠️ <b>Backup database gagal.</b>\n\n<code>{html.escape(str(e))}</code>",
                reply_markup=InlineKeyboardMarkup([[
                    InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_settings")
                ]])
            )
        finally:
            if backup_path and os.path.exists(backup_path):
                try:
                    os.remove(backup_path)
                except Exception:
                    pass

    elif query.data == "owner_caption_menu":
        if not is_owner(query.from_user.id):
            return

        context.user_data.pop("waiting_main_caption", None)
        await _show_caption_menu(query)

    elif query.data == "owner_caption_preview":
        if not is_owner(query.from_user.id):
            return

        await _show_main_caption_preview(query)

    elif query.data == "owner_set_caption":
        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_main_caption"] = True
        await _edit_query_text(
            query,
            "📝 <b>Ubah Caption Menu Utama</b>\n\n"
            "Ketik caption baru untuk menu utama.\n\n"
            "Placeholder yang tersedia:\n"
            "<code>{first_name}</code> - nama user\n"
            "<code>{username}</code> - username user\n"
            "<code>{user_id}</code> - ID user\n\n"
            "Emoji premium bisa langsung ditempel di caption.\n"
            "Untuk blockquote, gunakan HTML langsung:\n"
            "<code>&lt;blockquote expandable&gt;Benefit paket&lt;/blockquote&gt;</code>\n\n"
            "Contoh:\n"
            "<code>Hai {first_name}! Selamat datang di VIP 🔥</code>",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data="owner_caption_menu")]
            ])
        )

    elif query.data == "owner_reset_caption":
        if not is_owner(query.from_user.id):
            return

        reset_main_caption()
        await query.answer("✅ Caption dikembalikan ke default.", show_alert=True)
        await _show_caption_menu(query)

    elif query.data == "owner_editlink":

        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_edit_link"] = True
        await query.edit_message_text(
            "✏️ <b>Edit Link</b>\n\n"
            "Ketik dengan format:\n"
            "<code>URL_LINK|LIMIT_BARU</code>\n\n"
            "Contoh: <code>https://t.me/+AbCdEfG 3</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="owner_group_product")]
            ])
        )
    
    elif query.data == "daftar":
        if context.user_data.get("status_transaksi") == "pending":
            await query.answer(
                "⚠️ Kamu masih punya transaksi aktif! Selesaikan atau batalkan dulu.",
                show_alert=True
            )
            return

        await _show_vip_package_list(query, context)
    
    elif query.data == "rejoin":
        packages = get_visible_packages()
        if not packages:
            await query.answer("⚠️ Belum ada paket tersedia.", show_alert=True)
            return

        context.user_data["rejoin_selected_pkgs"] = set()

        def build_pkg_kb(selected_ids):
            rows = []
            for pkg in packages:
                harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
                mark = "✅" if pkg['id'] in selected_ids else "⬜"
                rows.append([InlineKeyboardButton(
                    f"{mark} {pkg['nama']} - Rp {harga_fmt}",
                    callback_data=f"rejoin_sel_pkg_{pkg['id']}"
                )])
            rows.append([
                InlineKeyboardButton("✅ Pilih & Lanjut", callback_data="rejoin_sel_done"),
                InlineKeyboardButton("❌ Batal", callback_data="menu")
            ])
            return InlineKeyboardMarkup(rows)

        caption = (
            "🔄 <b>Rejoin Grup</b>\n\n"
            "Pilih paket yang ingin kamu rejoin:"
        )
        keyboard = build_pkg_kb(set())
        banner_path = "assets/banner_rejoin.jpg"
        if os.path.exists(banner_path):
            await query.edit_message_media(
                media=InputMediaPhoto(
                    media=open(banner_path, "rb"),
                    caption=caption,
                    parse_mode="HTML"
                ),
                reply_markup=keyboard
            )
        else:
            await query.edit_message_caption(
                caption=caption,
                parse_mode="HTML",
                reply_markup=keyboard
            )

    elif query.data.startswith("rejoin_sel_pkg_"):
        pkg_id = int(query.data.replace("rejoin_sel_pkg_", ""))
        selected = context.user_data.get("rejoin_selected_pkgs", set())
        if pkg_id in selected:
            selected.discard(pkg_id)
        else:
            selected.add(pkg_id)
        context.user_data["rejoin_selected_pkgs"] = selected

        packages = get_visible_packages()

        def build_pkg_kb(selected_ids):
            rows = []
            for pkg in packages:
                harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
                mark = "✅" if pkg['id'] in selected_ids else "⬜"
                rows.append([InlineKeyboardButton(
                    f"{mark} {pkg['nama']} - Rp {harga_fmt}",
                    callback_data=f"rejoin_sel_pkg_{pkg['id']}"
                )])
            rows.append([
                InlineKeyboardButton("✅ Pilih & Lanjut", callback_data="rejoin_sel_done"),
                InlineKeyboardButton("❌ Batal", callback_data="menu")
            ])
            return InlineKeyboardMarkup(rows)

        await query.edit_message_reply_markup(reply_markup=build_pkg_kb(selected))
        await query.answer()

    elif query.data == "rejoin_sel_done":
        selected = context.user_data.get("rejoin_selected_pkgs", set())
        if not selected:
            await query.answer("⚠️ Pilih minimal 1 paket dulu!", show_alert=True)
            return

        context.user_data["rejoin_step"] = "waiting_approval"
        caption = (
            "🔄 <b>Rejoin Grup</b>\n\n"
            "📸 Kirim foto <b>bukti approve admin</b> pembayaran kamu sebelumnya."
        )
        keyboard = InlineKeyboardMarkup([
            [InlineKeyboardButton("❌ Batal", callback_data="menu")]
        ])
        banner_path = "assets/banner_rejoin.jpg"
        if os.path.exists(banner_path):
            await query.edit_message_media(
                media=InputMediaPhoto(
                    media=open(banner_path, "rb"),
                    caption=caption,
                    parse_mode="HTML"
                ),
                reply_markup=keyboard
            )
        else:
            await query.edit_message_caption(
                caption=caption,
                parse_mode="HTML",
                reply_markup=keyboard
            )

    # =========================
    # REJOIN - OPEN REJOIN REQUEST (Show Details)
    # =========================

    elif query.data.startswith("rejoin_open_"):
        if not is_owner(query.from_user.id):
            return

        user_id = int(query.data.replace("rejoin_open_", ""))
        await _show_rejoin_detail(query, context, user_id)
        return

    elif query.data.startswith("rejoin_verify_pkg_"):
        if not is_owner(query.from_user.id):
            return

        payload = query.data.replace("rejoin_verify_pkg_", "", 1)
        user_id_str, pkg_id_str = payload.split("_", 1)
        user_id = int(user_id_str)
        pkg_id = int(pkg_id_str)

        data = REJOIN_PENDING.get(user_id)
        if not data:
            await query.answer("⚠️ Data rejoin tidak ditemukan.", show_alert=True)
            return

        package_ids = set()
        for pkg in get_all_packages():
            try:
                package_ids.add(int(pkg["id"]))
            except (TypeError, ValueError):
                continue
        if pkg_id not in package_ids:
            await query.answer("⚠️ Paket tidak ditemukan.", show_alert=True)
            return

        selected_owner_ids = set()
        for item in data.get("owner_selected_pkgs", []) or []:
            try:
                selected_owner_ids.add(int(item))
            except (TypeError, ValueError):
                continue

        if pkg_id in selected_owner_ids:
            selected_owner_ids.remove(pkg_id)
        else:
            selected_owner_ids.add(pkg_id)

        data["owner_selected_pkgs"] = sorted(selected_owner_ids)
        REJOIN_PENDING[user_id] = data
        await _edit_query_caption_or_text(
            query,
            _format_rejoin_detail(user_id, data),
            reply_markup=_rejoin_detail_keyboard(user_id, data)
        )
        await query.answer("Paket verifikasi diperbarui.")
        return

    elif query.data.startswith("rejoin_approve_"):
        if not is_owner(query.from_user.id):
            return

        user_id = int(query.data.replace("rejoin_approve_", ""))
        data = REJOIN_PENDING.get(user_id)
        if not data:
            await query.answer("⚠️ Data rejoin tidak ditemukan.", show_alert=True)
            return

        selected_pkg_ids = data.get("owner_selected_pkgs", [])
        if not selected_pkg_ids:
            await query.answer("⚠️ Pilih paket verifikasi owner dulu.", show_alert=True)
            return

        packages = {}
        for package in get_all_packages():
            try:
                packages[int(package["id"])] = package
            except (TypeError, ValueError):
                continue
        normalized_selected_ids = []
        for pkg_id in selected_pkg_ids:
            try:
                pkg_id = int(pkg_id)
            except (TypeError, ValueError):
                continue
            if pkg_id in packages:
                normalized_selected_ids.append(pkg_id)

        if not normalized_selected_ids:
            await query.answer("⚠️ Paket verifikasi owner tidak valid.", show_alert=True)
            return

        teks_user = "✅ <b>Rejoin kamu telah disetujui!</b>\n\n🔗 Berikut link grup kamu:\n\n"
        for pkg_id in normalized_selected_ids:
            pkg = packages.get(pkg_id)
            if not pkg:
                continue
            if pkg.get("link"):
                link = pkg["link"]
            elif pkg.get("group_id"):
                try:
                    invite = await context.bot.create_chat_invite_link(
                        chat_id=pkg["group_id"],
                        member_limit=1
                    )
                    link = invite.invite_link
                    add_link(
                        invite.invite_link,
                        pkg["group_id"],
                        pkg["nama"],
                        user_id,
                        username=data.get("username") if data.get("username") != "-" else None
                    )
                except Exception as e:
                    log.error("[REJOIN] Gagal buat invite link untuk %s: %s", pkg["nama"], e)
                    link = "❌ Gagal generate link"
            else:
                link = "❌ Link tidak tersedia"
            teks_user += f"📌 <b>{pkg['nama']}</b>\n{link}\n\n"

        await context.bot.send_message(
            chat_id=user_id,
            text=teks_user,
            parse_mode="HTML"
        )

        data["owner_selected_pkgs"] = normalized_selected_ids
        _save_rejoin_transaction(user_id, data, "approved", package_ids=normalized_selected_ids)
        REJOIN_PENDING.pop(user_id, None)

        user_info = {
            "first_name": data.get("first_name", "User"),
            "id": user_id,
            "username": data.get("username", "-"),
        }
        pkg_names = [packages.get(pid, {}).get("nama", f"Package {pid}") for pid in normalized_selected_ids]
        order_data = {
            "paket_label": "REJOIN - " + ", ".join(pkg_names),
            "harga_bayar": 0,
        }
        await post_testimonial_to_channels(context, user_info, order_data, data.get("photo_id"))

        await _edit_query_caption_or_text(
            query,
            _format_rejoin_detail(user_id, data) + f"\n\n✅ APPROVED BY {html.escape(query.from_user.first_name or 'Owner')} — Link sudah dikirim ke user.",
            reply_markup=_processed_rejoin_keyboard()
        )

    elif query.data.startswith("rejoin_cancel_"):
        if not is_owner(query.from_user.id):
            return

        user_id = int(query.data.replace("rejoin_cancel_", ""))
        data = REJOIN_PENDING.get(user_id)
        if not data:
            await query.answer("⚠️ Data rejoin tidak ditemukan.", show_alert=True)
            return

        await context.bot.send_message(
            chat_id=user_id,
            text=(
                "❌ <b>Permintaan rejoin kamu ditolak.</b>\n\n"
                "Silakan hubungi admin jika ada pertanyaan."
            ),
            parse_mode="HTML"
        )

        _save_rejoin_transaction(user_id, data, "rejected")
        REJOIN_PENDING.pop(user_id, None)

        await _edit_query_caption_or_text(
            query,
            _format_rejoin_detail(user_id, data) + "\n\n❌ REJECTED.",
            reply_markup=_processed_rejoin_keyboard()
        )

    elif query.data == "menu":

        if context.user_data.get("status_transaksi") == "pending":
            await query.answer(
                "⚠️ Selesaikan atau batalkan pesananmu dulu!",
                show_alert=True
            )
            return

        context.user_data.pop("waiting_contact_message", None)
        context.user_data.pop("contact_topic", None)

        await show_main_menu(
            query,
            query.from_user
        )

    elif query.data == "cara_order":
        text = (
            "📖 <b>Cara Order</b>\n\n"
            "1. Pilih layanan yang kamu mau: <b>Paket VIP</b>, <b>Talent / VCS</b>, atau <b>Rejoin Grup</b>.\n"
            "2. Pilih paket yang tersedia.\n"
            "3. Pilih metode pembayaran: <b>QRIS</b> atau <b>Dana</b>.\n"
            "4. Transfer sesuai nominal yang muncul di invoice.\n"
            "5. Kirim foto bukti pembayaran ke bot.\n"
            "6. Tekan tombol konfirmasi setelah bukti terkirim.\n"
            "7. Tunggu proses pengecekan. Jika disetujui, link/akses akan dikirim otomatis.\n\n"
            "Jika ada kendala, gunakan menu <b>Bantuan Admin</b>."
        )
        await _edit_query_caption_or_text(
            query,
            text,
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("⬅️ Kembali", callback_data="menu")
            ]])
        )

    elif query.data.startswith("vip_pkg_"):
        if context.user_data.get("status_transaksi") == "pending":
            await query.answer(
                "⚠️ Kamu masih punya transaksi aktif! Selesaikan atau batalkan dulu.",
                show_alert=True
            )
            return

        pkg_id = int(query.data.replace("vip_pkg_", ""))

        pkg = get_package_by_id(pkg_id)

        if not pkg:
            log.warning("[VIP] Paket ID %s tidak ditemukan.", pkg_id)
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return

        if not is_package_visible(pkg):
            await query.answer("Paket ini sedang tidak tersedia.", show_alert=True)
            await _show_vip_package_list(query, context)
            return

        log.info("[VIP] User %s memilih paket '%s' (ID=%s, Harga=%s)", query.from_user.id, pkg["nama"], pkg_id, pkg["harga"])
        harga_fmt = f"{pkg['harga']:,}".replace(",", ".")

        context.user_data["paket_type"] = "single"
        context.user_data.pop("vip_multi_selected_ids", None)
        context.user_data["vip_selected_ids"] = [pkg_id]
        context.user_data["vip_harga_bayar"] = pkg["harga"]
        context.user_data["vip_discount_pct"] = 0
        context.user_data["status_transaksi"] = "pending"
        context.user_data["bukti_dikirim"] = False
        _set_transaction_back(context, "daftar")

        caption_order = _format_vip_caption(pkg, harga_fmt)
        await _show_payment_selection(query, context, caption_order, pkg.get("banner_file_id"))

    elif query.data == "vip_take_menu":
        if context.user_data.get("status_transaksi") == "pending":
            await query.answer(
                "⚠️ Kamu masih punya transaksi aktif!",
                show_alert=True
            )
            return

        context.user_data.pop("vip_multi_selected_ids", None)
        await _show_vip_take_menu(query)

    elif query.data == "vip_take_choose":

        packages = get_visible_packages()

        if len(packages) < 2:
            await query.answer(
                "Belum ada cukup paket untuk dipilih.",
                show_alert=True
            )
            return

        context.user_data.pop("vip_choosing_packages", None)
        context.user_data["vip_multi_selected_ids"] = []
        await _show_vip_multi_selection(query, context, packages, [])

    elif query.data.startswith("vip_multi_toggle_"):
        if context.user_data.get("status_transaksi") == "pending":
            await query.answer(
                "⚠️ Kamu masih punya transaksi aktif!",
                show_alert=True
            )
            return

        packages = get_visible_packages()
        package_ids = {pkg["id"] for pkg in packages}
        pkg_id = int(query.data.replace("vip_multi_toggle_", ""))
        if pkg_id not in package_ids:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return

        selected_ids = set(context.user_data.get("vip_multi_selected_ids", []))
        if pkg_id in selected_ids:
            selected_ids.remove(pkg_id)
        else:
            selected_ids.add(pkg_id)

        selected_ids = sorted(selected_ids)
        context.user_data["vip_multi_selected_ids"] = selected_ids
        await _show_vip_multi_selection(query, context, packages, selected_ids)

    elif query.data == "vip_multi_continue":
        if context.user_data.get("status_transaksi") == "pending":
            await query.answer(
                "⚠️ Kamu masih punya transaksi aktif!",
                show_alert=True
            )
            return

        selected_ids = context.user_data.get("vip_multi_selected_ids", [])
        if len(selected_ids) < 2:
            await query.answer("Pilih minimal 2 paket dulu.", show_alert=True)
            return

        packages = get_visible_packages()
        packages_by_id = {pkg["id"]: pkg for pkg in packages}
        selected = [packages_by_id[pid] for pid in selected_ids if pid in packages_by_id]
        if len(selected) < 2:
            await query.answer("Paket pilihan tidak valid. Pilih ulang paket.", show_alert=True)
            context.user_data["vip_multi_selected_ids"] = [pkg["id"] for pkg in selected]
            await _show_vip_multi_selection(query, context, packages, context.user_data["vip_multi_selected_ids"])
            return

        total = sum(pkg["harga"] for pkg in selected)
        diskon = int(total * 0.10)
        bayar = total - diskon

        context.user_data.pop("vip_multi_selected_ids", None)
        context.user_data["paket_type"] = "take_several"
        context.user_data["vip_selected_ids"] = [pkg["id"] for pkg in selected]
        context.user_data["vip_harga_bayar"] = bayar
        context.user_data["vip_discount_pct"] = 10
        context.user_data["status_transaksi"] = "pending"
        context.user_data["bukti_dikirim"] = False
        _set_transaction_back(context, "vip_multi_selection", selected_ids=selected_ids)

        caption_order = "✅ <b>Paket dipilih:</b>\n"
        for pkg in selected:
            caption_order += f"• {html.escape(pkg['nama'])} ─ {_format_rupiah(pkg['harga'])}\n"
        caption_order += (
            f"\nTotal Normal: {_format_rupiah(total)}\n"
            f"Diskon 10%: -{_format_rupiah(diskon)}\n"
            f"💰 <b>Total Bayar: {_format_rupiah(bayar)}</b>"
        )
        await _show_payment_selection(query, context, caption_order)

    elif query.data == "vip_take_all":
        if context.user_data.get("status_transaksi") == "pending":
            await query.answer(
                "⚠️ Kamu masih punya transaksi aktif!",
                show_alert=True
            )
            return


        packages = get_visible_packages()

        if not packages:
            await query.answer("Belum ada paket tersedia.", show_alert=True)
            return

        total = sum(p["harga"] for p in packages)
        diskon = int(total * 0.20)
        bayar = total - diskon

        log.info("[VIP] User %s pilih Take All | Total=%s Diskon20%%=%s Bayar=%s", query.from_user.id, total, diskon, bayar)
        context.user_data["paket_type"] = "take_all"
        context.user_data.pop("vip_multi_selected_ids", None)
        context.user_data["vip_selected_ids"] = [p["id"] for p in packages]
        context.user_data["vip_discount_pct"] = 20
        context.user_data["vip_take_all_total"] = total
        context.user_data["vip_take_all_discount"] = diskon
        context.user_data["vip_take_all_subtotal"] = bayar
        context.user_data["vip_harga_bayar"] = bayar
        context.user_data["status_transaksi"] = "pending"
        context.user_data["bukti_dikirim"] = False
        context.user_data["take_all_promo_done"] = False
        _set_transaction_back(context, "vip_take_menu")
        context.user_data.pop("waiting_take_all_promo", None)
        context.user_data.pop("promo_applied", None)
        context.user_data.pop("promo_discount_pct", None)
        context.user_data.pop("promo_discount_amount", None)

        await _show_take_all_promo_prompt(query, context)

    elif query.data == "take_all_enter_promo":
        if context.user_data.get("paket_type") != "take_all":
            await query.answer("Sesi Take All tidak ditemukan.", show_alert=True)
            return

        if context.user_data.get("promo_applied"):
            await query.answer("Promo sudah digunakan untuk pesanan ini.", show_alert=True)
            return

        context.user_data["waiting_take_all_promo"] = True
        subtotal = context.user_data.get("vip_take_all_subtotal", context.user_data.get("vip_harga_bayar", 0))
        await _edit_query_text(
            query,
            "🎟️ <b>Masukkan Kode Promo</b>\n\n"
            "Ketik kode promo yang kamu punya di chat ini.\n"
            f"Subtotal saat ini: <b>{_format_rupiah(subtotal)}</b>",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("Lewati Promo", callback_data="take_all_skip_promo")],
                [InlineKeyboardButton("⬅️ Kembali", callback_data="take_all_promo_back")],
                [InlineKeyboardButton("❌ Batalkan", callback_data="batal_transaksi")],
            ])
        )

    elif query.data == "take_all_promo_back":
        if context.user_data.get("paket_type") != "take_all":
            await query.answer("Sesi Take All tidak ditemukan.", show_alert=True)
            return

        if context.user_data.get("promo_applied"):
            await query.answer("Promo sudah digunakan. Lanjutkan pembayaran dari pesan terbaru.", show_alert=True)
            return

        context.user_data.pop("waiting_take_all_promo", None)
        await _show_take_all_promo_prompt(query, context)

    elif query.data == "take_all_skip_promo":
        if context.user_data.get("paket_type") != "take_all":
            await query.answer("Sesi Take All tidak ditemukan.", show_alert=True)
            return

        if context.user_data.get("promo_applied"):
            await query.answer("Promo sudah digunakan. Lanjutkan pembayaran dari pesan terbaru.", show_alert=True)
            return

        context.user_data.pop("waiting_take_all_promo", None)
        context.user_data["take_all_promo_done"] = True
        context.user_data["vip_harga_bayar"] = context.user_data.get(
            "vip_take_all_subtotal",
            context.user_data.get("vip_harga_bayar", 0)
        )

        await _show_payment_selection(query, context, _build_take_all_payment_caption(context))
    
    elif query.data == "batal_transaksi":
        promo_used = context.user_data.get("promo_applied")
        back = _get_transaction_back(context)
        extra_message_ids = list(context.user_data.get("qris_msg_ids") or [])
        qris_msg_id = context.user_data.get("qris_msg_id")
        if qris_msg_id:
            extra_message_ids.append(qris_msg_id)

        if promo_used:
            from services.promo_service import release_promo
            release_promo(promo_used)

        context.user_data.clear()
        await _delete_transaction_extra_messages(query, context, extra_message_ids)
        await _show_transaction_back_panel(query, context, back)

    elif query.data.startswith("vs_order_"):

        _, _, admin_id, paket = query.data.split("_", 3)
        admin_id = int(admin_id)
        config = get_vs(admin_id) or {}
        prices = config.get("prices", [])
        vs_price = next((item.get("harga") for item in prices if item.get("nama") == paket), 0)

        context.user_data["vs_admin_id"] = admin_id
        context.user_data["vs_paket"] = paket
        context.user_data["vs_harga_bayar"] = vs_price
        
        context.user_data["vs_status"] = "waiting_payment"
        _set_transaction_back(context, "vs_admin", admin_id=admin_id)

        keyboard = [
            [
                InlineKeyboardButton(
                    "❌ Batalkan",
                    callback_data="batal_transaksi"
                )
            ]
        ]

        caption_order = f"🎥 Order VCS\n\nPaket : {paket}"
        await _show_payment_selection(query, context, caption_order)
    

    elif query.data.startswith("vs_admin_"):

        admin_id = int(
            query.data.replace(
                "vs_admin_",
                ""
            )
        )
        await _show_vs_admin_catalog(query, context, admin_id)
    
    elif query.data == "vs_manage":

        if not is_admin(query.from_user.id):
            return

        # OWNER pilih admin dulu
        if is_owner(query.from_user.id):

            from services.admin_service import get_admins

            admins = get_admins()

            keyboard = []

            for admin in admins:
                keyboard.append([
                    InlineKeyboardButton(
                        f"👤 {admin['nama']}",
                        callback_data=f"vs_manage_admin_{admin['id']}"
                    )
                ])

            keyboard.append([
                InlineKeyboardButton(
                    "⬅️ Kembali",
                    callback_data="panel_admin"
                )
            ])

            await query.edit_message_text(
                "🎥 <b>Pilih Admin VCS</b>\n\n"
                "Silakan pilih admin yang ingin dikelola.",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup(keyboard)
            )
            return

        # ADMIN biasa langsung ke panel VS miliknya
        admin_data = get_admin_by_id(query.from_user.id)

        nama_admin = (
            admin_data["nama"]
            if admin_data
            else query.from_user.first_name
        )

        context.user_data["vs_target_admin"] = query.from_user.id

        keyboard = [
            [InlineKeyboardButton("📋 Lihat Paket", callback_data="vs_list_package")],
            [InlineKeyboardButton("➕ Tambah Paket", callback_data="vs_add_package")],
            [InlineKeyboardButton("🗑️ Hapus Paket", callback_data="vs_delete_package")],
            [InlineKeyboardButton("🖼 Ubah Foto", callback_data="vs_set_image")],
            [InlineKeyboardButton("📝 Ubah Caption", callback_data="vs_set_caption")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")]
        ]

        await query.edit_message_text(
            f"🎥 <b>VCS ADMIN - {nama_admin}</b>\n\n"
            "Silakan pilih menu pengelolaan VCS.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )
    elif query.data.startswith("vs_manage_admin_"):

        if not is_owner(query.from_user.id):
            return

        admin_id = int(
            query.data.replace(
                "vs_manage_admin_",
                ""
            )
        )

        admin_data = get_admin_by_id(admin_id)

        if not admin_data:
            await query.answer(
                "Admin tidak ditemukan",
                show_alert=True
            )
            return

        context.user_data["vs_target_admin"] = admin_id

        keyboard = [
            [
                InlineKeyboardButton(
                    "📋 Lihat Paket",
                    callback_data="vs_list_package"
                )
            ],
            [
                InlineKeyboardButton(
                    "➕ Tambah Paket",
                    callback_data="vs_add_package"
                )
            ],
            [
                InlineKeyboardButton(
                    "🖼 Ubah Foto",
                    callback_data="vs_set_image"
                )
            ],
            [
                InlineKeyboardButton(
                    "🗑️ Hapus Paket",
                    callback_data="vs_delete_package"
                )
            ],
            [
                InlineKeyboardButton(
                    "📝 Ubah Caption",
                    callback_data="vs_set_caption"
                )
            ],
            [
                InlineKeyboardButton(
                    "⬅️ Kembali",
                    callback_data="vs_manage"
                )
            ]
        ]

        await query.edit_message_text(
            f"🎥 <b>VCS ADMIN - {admin_data['nama']}</b>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "vs_add_package":

        context.user_data["waiting_vs_add"] = True
        await query.edit_message_text(
            "➕ <b>Tambah Paket VCS</b>\n\n"
            "Ketik dengan format:\n"
            "<code>NAMA_PAKET|HARGA</code>\n\n"
            "Contoh:\n"
            "<code>VCS 5 Menit|50000</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="vs_manage")]
            ])
        )
    elif query.data == "vs_set_image":

        context.user_data["vs_waiting_image"] = True

        await query.edit_message_text(
            "🖼 Kirim foto baru untuk katalog VCS.\n\n"
            "Foto berikutnya yang dikirim akan digunakan.",
            reply_markup=InlineKeyboardMarkup([
                [
                    InlineKeyboardButton(
                        "⬅️ Kembali",
                        callback_data="vs_manage"
                    )
                ]
            ])
        )

    elif query.data == "vs_set_caption":

        context.user_data["vs_waiting_caption"] = True

        await query.edit_message_text(
            "📝 Kirim caption baru untuk katalog VCS.\n\n"
            "Emoji premium bisa langsung ditempel di caption.",
            reply_markup=InlineKeyboardMarkup([
                [
                    InlineKeyboardButton(
                        "⬅️ Kembali",
                        callback_data="vs_manage"
                    )
                ]
            ])
        )
    
    elif query.data == "vs_list_package":

        target_admin = context.user_data.get(
            "vs_target_admin",
            query.from_user.id
        )

        config = get_vs(target_admin)

        if not config:
            await query.edit_message_text(
                "Belum ada paket VCS."
            )
            return

        teks = "📋 DAFTAR PAKET VCS\n\n"

        for item in config.get("prices", []):
            teks += (
                f"• {item['nama']}\n"
                f"💰 Rp {item['harga']}\n\n"
            )

        await query.edit_message_text(
            teks,
            reply_markup=InlineKeyboardMarkup([
                [
                    InlineKeyboardButton(
                        "⬅️ Kembali",
                        callback_data="vs_manage"
                    )
                ]
            ])
        )

    elif query.data == "vs_delete_package":

        if not is_admin(query.from_user.id):
            return

        target_admin = context.user_data.get(
            "vs_target_admin",
            query.from_user.id
        )

        config = get_vs(target_admin)

        if not config or not config.get("prices"):
            await query.edit_message_text(
                "Belum ada paket untuk dihapus.",
                reply_markup=InlineKeyboardMarkup([
                    [
                        InlineKeyboardButton(
                            "⬅️ Kembali",
                            callback_data="vs_manage"
                        )
                    ]
                ])
            )
            return

        keyboard = []

        for i, item in enumerate(config["prices"]):
            keyboard.append([
                InlineKeyboardButton(
                    f"🗑️ {item['nama']} - Rp {item['harga']}",
                    callback_data=f"vs_del_{i}"
                )
            ])

        keyboard.append([
            InlineKeyboardButton(
                "⬅️ Kembali",
                callback_data="vs_manage"
            )
        ])

        await query.edit_message_text(
            "🗑️ <b>Hapus Paket VCS</b>\n\nPilih paket yang ingin dihapus:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("vs_del_"):

        if not is_admin(query.from_user.id):
            return

        index = int(query.data.replace("vs_del_", ""))

        target_admin = context.user_data.get(
            "vs_target_admin",
            query.from_user.id
        )

        from services.vs_service import delete_vs_package

        success = delete_vs_package(target_admin, index)

        if not success:
            await query.answer(
                "❌ Paket tidak ditemukan.",
                show_alert=True
            )
            return

        await query.answer("✅ Paket berhasil dihapus!")

        config = get_vs(target_admin)

        if not config or not config.get("prices"):
            await query.edit_message_text(
                "✅ Semua paket telah dihapus.",
                reply_markup=InlineKeyboardMarkup([
                    [
                        InlineKeyboardButton(
                            "⬅️ Kembali",
                            callback_data="vs_manage"
                        )
                    ]
                ])
            )
            return

        keyboard = []

        for i, item in enumerate(config["prices"]):
            keyboard.append([
                InlineKeyboardButton(
                    f"🗑️ {item['nama']} - Rp {item['harga']}",
                    callback_data=f"vs_del_{i}"
                )
            ])

        keyboard.append([
            InlineKeyboardButton(
                "⬅️ Kembali",
                callback_data="vs_manage"
            )
        ])

        await query.edit_message_text(
            "🗑️ <b>Hapus Paket VCS</b>\n\nPilih paket yang ingin dihapus:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    # vs_menu
    elif query.data == "vs_menu":

        admins = get_vs_enabled_admins()

        if not admins:
            await query.answer("VCS sedang tidak tersedia", show_alert=True)
            return

        keyboard = []

        for admin in admins:
            keyboard.append([
                InlineKeyboardButton(
                    f"🎥 {admin['nama']}",
                    callback_data=f"vs_admin_{admin['id']}"
                )
            ])

        keyboard.append([
            InlineKeyboardButton("⬅️ Kembali", callback_data="menu")
        ])

        await query.edit_message_caption(
            caption="🎥 Silakan pilih Talent VCS:",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )
    # /

    elif query.data == "ganti_bukti":
        is_vip = context.user_data.get("status_transaksi") == "pending"
        is_vs = context.user_data.get("vs_status") == "waiting_payment"
        if not is_vip and not is_vs:
            await query.answer("Transaksi sudah tidak aktif.", show_alert=True)
            return

        context.user_data.pop("bukti", None)
        context.user_data["bukti_dikirim"] = False
        await _edit_query_text(
            query,
            "🔄 <b>Ganti Foto Bukti</b>\n\n"
            "Kirim ulang foto bukti pembayaran yang benar.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batalkan Transaksi", callback_data="batal_transaksi")]
            ])
        )

    elif query.data == "konfirmasi":
        log.info("[KONFIRMASI] User %s menekan konfirmasi pembayaran.", query.from_user.id)
        if context.user_data.get("status_transaksi") != "pending":
            log.warning("[KONFIRMASI] User %s tidak dalam status pending, diabaikan.", query.from_user.id)
            await query.answer(
                "❌ Transaksimu sudah diproses atau kedaluwarsa.",
                show_alert=True
            )
            return

        user = query.from_user
        photo_id = context.user_data.get("bukti")

        if not photo_id:
            log.warning("[KONFIRMASI] User %s tidak punya bukti foto.", query.from_user.id)
            await query.answer(
                "❌ Foto bukti belum terekam, silakan kirim ulang.",
                show_alert=True
            )
            return

        paket_type = context.user_data.get("paket_type", "single")
        selected_ids = context.user_data.get("vip_selected_ids", [])
        harga_bayar = context.user_data.get("vip_harga_bayar", 0)
        promo_used = context.user_data.get("promo_applied")
        discount_pct = context.user_data.get("vip_discount_pct", 0)
        promo_discount_pct = context.user_data.get("promo_discount_pct")

        harga_fmt = f"{harga_bayar:,}".replace(",", ".")


        if paket_type == "single" and selected_ids:
            pkg = get_package_by_id(selected_ids[0])
            nama = pkg["nama"] if pkg else "Paket VIP"
            paket_label = f"{nama} - Rp {harga_fmt}"
        elif paket_type == "take_several":
            detail_paket = ""
            for pid in selected_ids:
                pkg = get_package_by_id(pid)
                if pkg:
                    h_fmt = f"{pkg['harga']:,}".replace(",", ".")
                    detail_paket += f"\n• {pkg['nama']} - Rp {h_fmt}"
            paket_label = (
                f"Take Beberapa ({len(selected_ids)} Paket, Diskon 10%) "
                f"- Rp {harga_fmt}"
                f"{detail_paket}"
            )
        else:
            if promo_used:
                promo_label = (
                    f", Promo {promo_used} {promo_discount_pct}%"
                    if promo_discount_pct is not None
                    else f", Promo {promo_used}"
                )
                paket_label = (
                    f"Take All (Diskon {discount_pct}%{promo_label}) "
                    f"- Rp {harga_fmt}"
                )
            else:
                paket_label = f"Take All (Diskon 20%) - Rp {harga_fmt}"

        order = {
            "paket_type": paket_type,
            "paket_label": paket_label,
            "selected_ids": selected_ids,
            "harga_bayar": harga_bayar,
            "promo_used": promo_used,
            "discount_pct": discount_pct
        }

        transaction_code = context.user_data.get("transaction_code") or _generate_transaction_code(order, user.id)
        order["transaction_code"] = transaction_code

        log.info(
            "[KONFIRMASI] Mengirim approval: user=%s | code=%s | type=%s | label=%s | harga=%s | ids=%s",
            user.id, transaction_code, paket_type, paket_label, harga_bayar, selected_ids
        )
        await send_approval_to_admin(context, user, order, photo_id)
        context.user_data["status_transaksi"] = "menunggu_admin"

        qris_msg_ids = context.user_data.pop("qris_msg_ids", [])
        qris_msg_id = context.user_data.pop("qris_msg_id", None)
        if qris_msg_id:
            qris_msg_ids.append(qris_msg_id)
        for qris_msg_id in set(qris_msg_ids):
            try:
                await context.bot.delete_message(chat_id=user.id, message_id=qris_msg_id)
            except Exception:
                pass

        await query.edit_message_text(
            "✅ <b>Bukti berhasil dikirim ke admin!</b>\n"
            f"🆔 Kode Transaksi: <code>{transaction_code}</code>\n"
            "⏳ Mohon tunggu konfirmasi persetujuan masuk ke obrolan ini.",
            parse_mode="HTML"
        )

    elif query.data == "konfirmasi_vs":

        admin_id = context.user_data.get("vs_admin_id")
        paket = context.user_data.get("vs_paket")
        photo_id = context.user_data.get("bukti")
        transaction_code = context.user_data.get("transaction_code")

        if not photo_id:
            await query.answer(
                "Bukti tidak ditemukan",
                show_alert=True
            )
            return

        from services.payment_service import send_vs_approval

        await send_vs_approval(
            context,
            query.from_user,
            admin_id,
            paket,
            photo_id,
            transaction_code
        )

        context.user_data["vs_status"] = "waiting_admin"

        qris_msg_ids = context.user_data.pop("qris_msg_ids", [])
        qris_msg_id = context.user_data.pop("qris_msg_id", None)
        if qris_msg_id:
            qris_msg_ids.append(qris_msg_id)
        for qris_msg_id in set(qris_msg_ids):
            try:
                await context.bot.delete_message(
                    chat_id=query.from_user.id,
                    message_id=qris_msg_id
                )
            except Exception:
                pass

        await query.edit_message_text(
            "✅ Bukti pembayaran berhasil dikirim.\n\n"
            f"🆔 Kode Transaksi: <code>{transaction_code or '-'}</code>\n"
            "⏳ Menunggu konfirmasi talent.",
            parse_mode="HTML"
        )
    
    elif query.data.startswith("reject_reason_"):
        if not is_owner(query.from_user.id):
            await query.answer(
                "❌ Hanya owner yang dapat reject transaksi",
                show_alert=True
            )
            return

        reason_data = query.data.replace("reject_reason_", "", 1)
        user_id_str, reason_key = reason_data.split("_", 1)
        user_id = int(user_id_str)
        reason = REJECTION_REASONS.get(reason_key, REJECTION_REASONS["other"])

        if user_id in PROCESSED_APPROVALS:
            await query.answer("⚠️ Transaksi ini sedang diproses staff lain.", show_alert=True)
            return

        data = get_pending(user_id)
        if not data:
            await query.answer("⚠️ Data transaksi tidak ditemukan.", show_alert=True)
            return

        order = data.get("order", {}) if isinstance(data, dict) else {}
        caption = context.user_data.pop(
            f"reject_caption_{user_id}",
            query.message.caption or query.message.text or _format_order_detail(user_id, data)
        )

        PROCESSED_APPROVALS.add(user_id)
        try:
            log.info("[REJECT] Memulai proses reject user_id=%s | reason=%s", user_id, reason)
            await process_rejection(context, user_id, order, reason=reason)
            log.info("[REJECT] Berhasil reject user_id=%s", user_id)
            new_caption = (
                caption +
                f"\n\n❌ STATUS: REJECTED BY {query.from_user.first_name}\n"
                f"Alasan: {reason}"
            )
            await update_all_approval_messages(context, user_id, new_caption)
            await query.answer("❌ Transaksi berhasil di-reject!", show_alert=True)
        except Exception as e:
            log.error("[REJECT] ERROR saat reject user_id=%s: %s", user_id, e, exc_info=True)
            await query.answer(f"ERROR: {e}", show_alert=True)
        finally:
            PROCESSED_APPROVALS.discard(user_id)

    elif query.data.startswith("approve_") or query.data.startswith("reject_"):
        if not is_owner(query.from_user.id):
            await query.answer(
                "❌ Hanya owner yang dapat approve transaksi",
                show_alert=True
            )
            return

        action, user_id_str = query.data.split("_", 1)
        user_id = int(user_id_str)

        log.info("[APPROVAL] Owner %s mencoba %s user_id=%s", query.from_user.id, action, user_id)

        if user_id in PROCESSED_APPROVALS:
            log.warning("[APPROVAL] user_id=%s sudah dalam PROCESSED_APPROVALS, diabaikan.", user_id)
            await query.answer(
                "⚠️ Transaksi ini sudah diproses staff lain.",
                show_alert=True
            )
            return

        data = get_pending(user_id)
        if not data:
            log.warning("[APPROVAL] Tidak ada pending data untuk user_id=%s", user_id)
            await query.answer("⚠️ Data transaksi tidak ditemukan.", show_alert=True)
            return
        order = data.get("order", {}) if isinstance(data, dict) else {}
        log.info("[APPROVAL] Data order: %s", order)
        caption = query.message.caption or query.message.text or "📦 KONFIRMASI PEMBAYARAN"

        if action == "reject":
            await _show_rejection_reasons(query, context, user_id)
            return

        if action == "approve":
            try:
                PROCESSED_APPROVALS.add(user_id)
                log.info("[APPROVE] Memulai proses approve user_id=%s", user_id)
                await process_approval(context, user_id, order)
                log.info("[APPROVE] Berhasil approve user_id=%s", user_id)
                user_info = data.get("user", {}) if isinstance(data, dict) else {}
                photo_id = data.get("photo_id") if isinstance(data, dict) else None
                created_at = data.get("created_at") if isinstance(data, dict) else None
                await post_testimonial_to_channels(context, user_info, order, photo_id, created_at)
                new_caption = (
                    caption +
                    f"\n\n✅ STATUS: APPROVED BY {query.from_user.first_name}"
                )
                await update_all_approval_messages(context, user_id, new_caption)
                await query.answer("✅ Transaksi berhasil di-approve!", show_alert=True)
            except Exception as e:
                log.error("[APPROVE] ERROR saat approve user_id=%s: %s", user_id, e, exc_info=True)
                await query.answer(f"ERROR: {e}", show_alert=True)
                return
            finally:
                PROCESSED_APPROVALS.discard(user_id)
    
    elif query.data.startswith("vs_approve_"):

        if not is_owner(query.from_user.id):
            await query.answer(
                "❌ Hanya owner yang dapat approve order VCS.",
                show_alert=True
            )
            return

        user_id = int(query.data.replace("vs_approve_", ""))

        if user_id in VS_PROCESSED_APPROVALS:
            await query.answer("⚠️ Order VCS sudah diproses.", show_alert=True)
            return

        VS_PROCESSED_APPROVALS.add(user_id)

        data = VS_PENDING_APPROVALS.get(user_id, {})
        messages = data.get("messages", [])
        paket = data.get("paket", "-")
        admin_id = data.get("admin_id")

        user_chat = await context.bot.get_chat(user_id)

        VS_APPROVED_CACHE[user_id] = {"paket": paket, "admin_id": admin_id}

        await context.bot.send_message(
            chat_id=user_id,
            text=(
                f"✅ Pembayaran berhasil dikonfirmasi.\n\n"
                f"😘 Hai {user_chat.first_name} ganteng, terima kasih banyak sudah order VCS💋.\n\n"
                f"📦 Paket: <b>{paket}</b>\n\n"
                "💞 Aku senang banget kamu sudah mampir dan memilih aku.\n"
                "✨ Semoga pengalaman kali ini bikin kamu makin senang dan puas ya.\n\n"
                "🥰 Jangan kapok ya sayang, klik tombol di bawah untuk langsung terhubung dengan admin ya....🫦👅"
            ),
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("💬 Hubungi Admin", callback_data=f"vs_contact_admin_{user_id}")
            ]])
        )

        new_caption = query.message.caption + f"\n\n✅ APPROVED BY {query.from_user.first_name}"

        for item in messages:
            try:
                await context.bot.edit_message_caption(
                    chat_id=item["chat_id"],
                    message_id=item["message_id"],
                    caption=new_caption,
                    reply_markup=_processed_vs_keyboard()
                )
            except Exception:
                pass

        VS_PENDING_APPROVALS.pop(user_id, None)
        VS_PROCESSED_APPROVALS.discard(user_id)

    elif query.data.startswith("vs_contact_admin_"):

        user_id = int(query.data.replace("vs_contact_admin_", ""))

        cache = VS_APPROVED_CACHE.get(user_id, {})
        paket = cache.get("paket", "-")
        admin_id = cache.get("admin_id")

        user = query.from_user
        username = f"@{user.username}" if user.username else f"ID: {user.id}"

        if admin_id:
            await context.bot.send_message(
                chat_id=admin_id,
                text=(
                    f"🎥 <b>User siap untuk VCS!</b>\n\n"
                    f"👤 Nama: {user.first_name}\n"
                    f"📌 {username}\n"
                    f"🆔 ID: <code>{user.id}</code>\n"
                    f"📦 Paket: <b>{paket}</b>\n\n"
                    f"Segera hubungi user:"
                ),
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([[
                    InlineKeyboardButton("💬 Chat User", url=f"tg://user?id={user.id}")
                ]])
            )

        VS_APPROVED_CACHE.pop(user_id, None)

        await query.edit_message_reply_markup(reply_markup=None)
        await query.answer("✅ Admin sudah diberitahu, tunggu dihubungi ya!", show_alert=True)

    elif query.data.startswith("vs_reject_"):

        if not is_owner(query.from_user.id):
            await query.answer(
                "❌ Hanya owner yang dapat reject order VCS.",
                show_alert=True
            )
            return

        user_id = int(query.data.replace("vs_reject_", ""))

        if user_id in VS_PROCESSED_APPROVALS:
            await query.answer("⚠️ Order VCS sudah diproses.", show_alert=True)
            return

        VS_PROCESSED_APPROVALS.add(user_id)

        await context.bot.send_message(
            chat_id=user_id,
            text=(
                "❌ Pembayaran ditolak.\n\n"
                "Silakan hubungi admin."
            )
        )

        new_caption = query.message.caption + f"\n\n❌ REJECTED BY {query.from_user.first_name}"

        data = VS_PENDING_APPROVALS.get(user_id, {})
        messages = data.get("messages", [])

        for item in messages:
            try:
                await context.bot.edit_message_caption(
                    chat_id=item["chat_id"],
                    message_id=item["message_id"],
                    caption=new_caption,
                    reply_markup=_processed_vs_keyboard()
                )
            except Exception:
                pass

        VS_PENDING_APPROVALS.pop(user_id, None)
        VS_PROCESSED_APPROVALS.discard(user_id)

    # =========================
    # ADMIN PANEL BUTTON
    # =========================

    elif query.data == "admin_cekpromo":

        if not is_admin(query.from_user.id):
            return

        promos = get_all_promos()

        if not promos:
            await query.edit_message_text(
                "⚠️ Belum ada promo aktif.",
                reply_markup=InlineKeyboardMarkup([
                    [
                        InlineKeyboardButton(
                            "⬅️ Kembali",
                            callback_data="panel_admin"
                        )
                    ]
                ])
            )
            return

        teks = "📊 DAFTAR PROMO AKTIF\n\n"

        for kode, data in promos.items():

            sisa = data["kuota"] - data["terpakai"]

            if data.get("type") == "percent":
                nilai = f"Diskon {data['persen']}%"
            else:
                nilai = f"Rp {data.get('harga', '-')}"

            teks += (
                f"🎟️ {kode}\n"
                f"💰 {nilai}\n"
                f"👥 Sisa Kuota : {sisa}/{data['kuota']}\n\n"
            )

        await query.edit_message_text(
            teks,
            reply_markup=InlineKeyboardMarkup([
                [
                    InlineKeyboardButton(
                        "⬅️ Kembali",
                        callback_data="panel_admin"
                    )
                ]
            ])
        )


    elif query.data == "admin_listlink":

        if not is_admin(query.from_user.id):
            return

        packages = get_all_packages()
        if not packages:
            await query.edit_message_text(
                "⚠️ Belum ada paket yang terdaftar.",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_info")]
                ])
            )
            return

        keyboard = [
            [InlineKeyboardButton(f"📦 {pkg['nama']}", callback_data=f"listlink_paket_{pkg['id']}")]
            for pkg in packages
        ]
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_info")])

        await query.edit_message_text(
            "🖇️ <b>List Link</b>\n\nPilih paket:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("listlink_paket_"):

        if not is_admin(query.from_user.id):
            return

        pkg_id = query.data.replace("listlink_paket_", "")
        await _show_member_dashboard(query, context, pkg_id)

    elif query.data.startswith("listlink_userid_"):

        if not is_admin(query.from_user.id):
            return

        pkg_id = query.data.replace("listlink_userid_", "")
        context.user_data["listlink_last_pkg"] = pkg_id
        await _show_member_picker(query, pkg_id, mode="user")

    elif query.data.startswith("listlink_links_"):

        if not is_admin(query.from_user.id):
            return

        pkg_id = query.data.replace("listlink_links_", "")
        await _show_member_picker(query, pkg_id, mode="link")

    elif query.data.startswith("listlink_search_"):

        if not is_admin(query.from_user.id):
            return

        pkg_id = query.data.replace("listlink_search_", "")
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("❌ Paket tidak ditemukan.", show_alert=True)
            return

        context.user_data["waiting_member_search"] = pkg_id
        await _edit_query_text(
            query,
            f"🔍 <b>Cari User — {html.escape(pkg['nama'])}</b>\n\n"
            "Ketik User ID atau username.\n"
            "Contoh: <code>123456789</code> atau <code>@username</code>",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data=f"listlink_paket_{pkg_id}")]
            ])
        )

    elif query.data.startswith("listlink_member_"):

        if not is_admin(query.from_user.id):
            return

        payload = query.data.replace("listlink_member_", "")
        parts = payload.split("_")
        row_id, pkg_id = parts[0], parts[1]
        mode = parts[2] if len(parts) > 2 and parts[2] in {"user", "link"} else "user"
        await _show_member_detail(query, row_id, pkg_id, mode=mode)

    elif query.data.startswith("listlink_resend_"):

        if not is_owner(query.from_user.id):
            return

        payload = query.data.replace("listlink_resend_", "")
        row_id, pkg_id = payload.split("_", 1)
        item = get_link_by_id(int(row_id))
        if not item or item.get("status") != "Aktif":
            await query.answer("Data member tidak ditemukan atau sudah tidak aktif.", show_alert=True)
            return

        try:
            await context.bot.send_message(
                chat_id=item["user_id"],
                text=(
                    "🔄 <b>Link akses kamu dikirim ulang.</b>\n\n"
                    f"📦 Paket: <b>{html.escape(item.get('group_name') or '-')}</b>\n"
                    f"🔗 {html.escape(item.get('link') or '-')}"
                ),
                parse_mode="HTML",
                disable_web_page_preview=True
            )
            await query.answer("✅ Link berhasil dikirim ulang ke user.", show_alert=True)
        except Exception as e:
            await query.answer(f"⚠️ Gagal mengirim link: {e}", show_alert=True)

    elif query.data.startswith("listlink_hapus_member_"):

        if not is_owner(query.from_user.id):
            await query.answer("❌ Hanya owner yang bisa menghapus akses.", show_alert=True)
            return

        payload = query.data.replace("listlink_hapus_member_", "")
        row_id, pkg_id = payload.split("_", 1)
        item = get_link_by_id(int(row_id))
        if not item:
            await query.answer("❌ Data tidak ditemukan.", show_alert=True)
            return

        username = item.get("username") or "-"
        username_text = f"@{username}" if username and username != "-" else "-"
        await _edit_query_text(
            query,
            "⚠️ <b>Konfirmasi Hapus Akses</b>\n\n"
            f"👤 User ID: <code>{item['user_id']}</code>\n"
            f"📛 Username: {html.escape(username_text)}\n"
            f"📦 Paket: <b>{html.escape(item.get('group_name') or '-')}</b>\n\n"
            "Data link akan dihapus dari database bot.",
            reply_markup=InlineKeyboardMarkup([
                [
                    InlineKeyboardButton("✅ Hapus", callback_data=f"listlink_konfirm_hapus_{row_id}_{pkg_id}"),
                    InlineKeyboardButton("❌ Batal", callback_data=f"listlink_member_{row_id}_{pkg_id}"),
                ]
            ])
        )

    elif query.data.startswith("listlink_tambah_"):

        if not is_owner(query.from_user.id):
            return

        pkg_id = query.data.replace("listlink_tambah_", "")
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("❌ Paket tidak ditemukan.", show_alert=True)
            return

        context.user_data["waiting_manual_add"] = pkg_id
        await query.edit_message_text(
            f"➕ <b>Tambah Data User Manual</b>\n"
            f"📦 Paket: <b>{pkg['nama']}</b>\n\n"
            f"Kirim data dengan format:\n"
            f"<code>user_id|username|link</code>\n\n"
            f"Contoh:\n"
            f"<code>739736834|Fahmium|https://t.me/+AbCdEf</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data=f"listlink_paket_{pkg_id}")]
            ])
        )

    elif query.data.startswith("listlink_hapus_prompt_"):

        if not is_owner(query.from_user.id):
            return

        pkg_id = query.data.replace("listlink_hapus_prompt_", "")
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("❌ Paket tidak ditemukan.", show_alert=True)
            return

        links = get_links_by_chat_id(pkg["group_id"]) if pkg["group_id"] else []
        if not links:
            links = get_links_by_group_name(pkg["nama"])

        teks = f"👤 <b>User ID — {pkg['nama']}</b>\n\n"
        for i, item in enumerate(links, 1):
            un = item.get("username") or "-"
            uname = f"@{un}" if un and un != "-" else "-"
            teks += f"{i}. <code>{item['user_id']}</code> | {uname}\n"

        teks += "\n🗑️ Ketik <b>User ID</b> yang ingin dihapus:"

        context.user_data["waiting_hapus_manual"] = pkg_id
        await query.edit_message_text(
            teks,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data=f"listlink_userid_{pkg_id}")]
            ])
        )

    elif query.data.startswith("listlink_konfirm_hapus_"):

        if not is_owner(query.from_user.id):
            return

        parts = query.data.replace("listlink_konfirm_hapus_", "").split("_")
        row_id, pkg_id = parts[0], parts[1]
        item = get_link_by_id(int(row_id))
        if not item:
            await query.answer("❌ Data tidak ditemukan.", show_alert=True)
            return

        delete_link_by_id(int(row_id))
        un = item.get("username") or "-"
        uname = f"@{un}" if un and un != "-" else "-"
        await query.edit_message_text(
            f"✅ <b>Data berhasil dihapus!</b>\n\n"
            f"👤 User ID : <code>{item['user_id']}</code>\n"
            f"📛 Username: {uname}\n"
            f"📦 Paket   : {item['group_name']}",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_userid_{pkg_id}")]
            ])
        )

    elif query.data.startswith("listlink_batal_hapus_"):

        if not is_owner(query.from_user.id):
            return

        pkg_id = query.data.replace("listlink_batal_hapus_", "")
        context.user_data.pop("waiting_hapus_manual", None)
        await query.edit_message_text(
            "❌ Penghapusan dibatalkan.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_userid_{pkg_id}")]
            ])
        )


    elif query.data == "admin_infogrup":

        if not is_admin(query.from_user.id):
            return



        packages = get_all_packages()

        seen_group_ids = set()
        rows = []
        total_semua = 0

        for pkg in packages:
            group_id = pkg.get("group_id")
            nama = pkg["nama"]

            if not group_id:
                rows.append((nama, None, "link"))
                continue

            if group_id in seen_group_ids:
                continue
            seen_group_ids.add(group_id)

            try:
                count = await context.bot.get_chat_member_count(group_id)
                total_semua += count
                rows.append((nama, count, "ok"))
            except Exception:
                rows.append((nama, None, "error"))

        teks = (
            "╔══════════════════════╗\n"
            "║  📊 <b>INFO MEMBER GRUP VIP</b>  ║\n"
            "╚══════════════════════╝\n\n"
        )

        pkg_buttons = []
        if rows:
            teks += f"👥 <b>Total Semua Member: {total_semua} orang</b>\n"
            teks += "─────────────────────"

            for pkg, (nama, count, status) in zip(packages, rows):
                cb = f"listlink_paket_{pkg['id']}"
                if status == "ok":
                    sub = f"└ 👤 {count} member"
                elif status == "link":
                    sub = "└ 🔗 Link statis"
                else:
                    sub = "└ ⚠️ Gagal"
                sub_display = sub.replace("└", "|")
                pkg_buttons.append([InlineKeyboardButton(f"📌 {nama}    {sub_display}", callback_data=cb)])
        else:
            teks += "📭 Belum ada paket VIP yang terdaftar."

        pkg_buttons.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_info")])

        await query.edit_message_text(
            teks,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(pkg_buttons)
        )


    elif query.data == "admin_cekuser":

        if not is_admin(query.from_user.id):
            return

        context.user_data["waiting_cek_user"] = True
        await query.edit_message_text(
            "🔍 <b>Cek User</b>\n\n"
            "Ketik USER_ID yang ingin dicek:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("❌ Batal", callback_data="owner_group_info")
            ]])
        )



    elif query.data == "owner_buatpromo":

        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_promo_add"] = True
        await query.edit_message_text(
            "➕ <b>Buat Kode Promo</b>\n\n"
            "Ketik dengan format:\n"
            "<code>KODE KUOTA PERSEN%</code>\n\n"
            "Contoh: <code>HEMAT30 5 30%</code>\n\n"
            "Atau harga tetap:\n"
            "<code>KODE KUOTA HARGA</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="owner_group_product")]
            ])
        )


    elif query.data == "owner_hapuslink":

        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_hapus_link"] = True
        await query.edit_message_text(
            "🗑️ <b>Hapus Link</b>\n\n"
            "Ketik URL link yang ingin dihapus:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="owner_group_product")]
            ])
        )

    elif query.data == "owner_adminmenu":

        if not is_owner(query.from_user.id):
            return

        from services.admin_service import get_admins
        admins = get_admins()

        teks = "👮 DAFTAR ADMIN\n\n"

        if not admins:

            teks += "Belum ada admin."

        else:

            for admin in admins:

                    teks += (
                        f"• <code>{admin['id']}</code> - "
                        f"{admin['nama']}\n"
                    )

        keyboard = [

            [
                InlineKeyboardButton(
                    "➕ Tambah Admin",
                    callback_data="owner_addadmin"
                )
            ],

            [
                InlineKeyboardButton(
                    "🗑️ Hapus Admin",
                    callback_data="owner_removeadmin"
                )
            ],
            [
                InlineKeyboardButton(
                    "🟢🔴 on/of Admin",
                    callback_data="owner_vs_menu"
                )
            ],

            [
                InlineKeyboardButton(
                    "⬅️ Kembali",
                    callback_data="panel_admin"
                )
            ]
        ]

        await query.edit_message_text(
            teks,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_addadmin":
        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_add_admin"] = True
        await query.edit_message_text(
            "➕ <b>Tambah Admin</b>\n\n"
            "Ketik dengan format:\n"
            "<code>USER_ID NAMA_ADMIN</code>\n\n"
            "Contoh: <code>123456789 Budi</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="owner_adminmenu")]
            ])
        )

    elif query.data == "owner_removeadmin":
        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_remove_admin"] = True
        await query.edit_message_text(
            "🗑️ <b>Hapus Admin</b>\n\n"
            "Ketik USER_ID admin yang ingin dihapus:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="owner_adminmenu")]
            ])
        )

    
            
    elif query.data == "owner_vip_menu":
        if not is_owner(query.from_user.id):
            return

        await _show_owner_vip_menu(query, context, clear_state=True)

    elif query.data == "owner_vip_add":
        if not is_owner(query.from_user.id):
            return

        for key in [
            "waiting_vip_add",
            "waiting_vip_add_name",
            "waiting_vip_add_price",
            "waiting_vip_add_group",
            "waiting_vip_add_link",
        ]:
            context.user_data.pop(key, None)
        context.user_data["vip_add_draft"] = {}
        context.user_data["waiting_vip_add_name"] = True
        await query.edit_message_text(
            "➕ <b>Tambah Paket VIP</b>\n\n"
            "Step 1/5\n"
            "Ketik <b>nama paket</b>:\n\n"
            "Contoh: <code>VIP PREMIUM</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]
            ])
        )

    elif query.data in ("owner_vip_add_access_group", "owner_vip_add_access_link"):
        if not is_owner(query.from_user.id):
            return

        draft = context.user_data.get("vip_add_draft") or {}
        if not draft.get("nama") or not draft.get("harga"):
            await query.answer("Sesi tambah paket tidak lengkap. Mulai ulang.", show_alert=True)
            return

        context.user_data.pop("waiting_vip_add_group", None)
        context.user_data.pop("waiting_vip_add_link", None)
        if query.data == "owner_vip_add_access_group":
            context.user_data["waiting_vip_add_group"] = True
            await query.edit_message_text(
                "➕ <b>Tambah Paket VIP</b>\n\n"
                "Step 4/5\n"
                "Ketik <b>Group ID</b> untuk auto generate invite link.\n\n"
                "Contoh: <code>-1003956111712</code>",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]
                ])
            )
        else:
            context.user_data["waiting_vip_add_link"] = True
            await query.edit_message_text(
                "➕ <b>Tambah Paket VIP</b>\n\n"
                "Step 4/5\n"
                "Ketik <b>link statis</b> untuk paket ini.\n\n"
                "Contoh: <code>https://t.me/+AbCdEfG</code>",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]
                ])
            )

    elif query.data == "owner_vip_delete":
        if not is_owner(query.from_user.id):
            return


        packages = get_all_packages()

        if not packages:
            await query.edit_message_text(
                "Belum ada paket untuk dihapus.",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")]
                ])
            )
            return

        keyboard = []
        for pkg in packages:
            harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
            keyboard.append([InlineKeyboardButton(
                f"🗑️ {pkg['nama']} - Rp {harga_fmt}",
                callback_data=f"owner_vip_del_{pkg['id']}"
            )])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")])

        await query.edit_message_text(
            "🗑️ Pilih paket yang ingin dihapus:",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("owner_vip_del_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_del_", ""))

        success = delete_package(pkg_id)

        if not success:
            await query.answer("❌ Paket tidak ditemukan.", show_alert=True)
            return

        await query.answer("✅ Paket berhasil dihapus!")

        packages = get_all_packages()

        if not packages:
            await query.edit_message_text(
                "✅ Semua paket telah dihapus.",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")]
                ])
            )
            return

        keyboard = []
        for pkg in packages:
            harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
            keyboard.append([InlineKeyboardButton(
                f"🗑️ {pkg['nama']} - Rp {harga_fmt}",
                callback_data=f"owner_vip_del_{pkg['id']}"
            )])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")])

        await query.edit_message_text(
            "🗑️ Pilih paket yang ingin dihapus:",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_vip_edit_select":
        if not is_owner(query.from_user.id):
            return

        packages = get_all_packages()
        if not packages:
            await query.edit_message_text(
                "Belum ada paket untuk diedit.",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")]
                ])
            )
            return

        keyboard = []
        for pkg in packages:
            harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
            status = _package_visibility_status(pkg)
            keyboard.append([InlineKeyboardButton(
                f"{status} ✏️ {pkg['nama']} - Rp {harga_fmt}",
                callback_data=f"owner_vip_edit_detail_{pkg['id']}"
            )])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")])

        await query.edit_message_text(
            "✏️ Pilih paket yang ingin diedit:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_vip_visibility":
        if not is_owner(query.from_user.id):
            return

        await _show_owner_vip_menu(query, context)

    elif query.data.startswith("owner_vip_toggle_visible_detail_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_toggle_visible_detail_", ""))
        pkg = toggle_package_visibility(pkg_id)
        if not pkg:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return
        await query.answer(f"✅ Tampilan paket sekarang {_package_visibility_status(pkg)}")
        await _show_vip_edit_detail(query, pkg_id)

    elif query.data.startswith("owner_vip_toggle_visible_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_toggle_visible_", ""))
        pkg = toggle_package_visibility(pkg_id)
        if not pkg:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return
        await query.answer(f"✅ Tampilan paket sekarang {_package_visibility_status(pkg)}")
        await _show_owner_vip_menu(query, context)

    elif query.data.startswith("owner_vip_edit_detail_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_edit_detail_", ""))
        await _show_vip_edit_detail(query, pkg_id)

    elif query.data.startswith("owner_vip_edit_name_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_edit_name_", ""))
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_vip_id"] = pkg_id
        context.user_data["waiting_vip_edit_name"] = True
        await query.edit_message_text(
            f"✏️ <b>Edit Nama Paket</b>\n\n"
            f"Nama saat ini: <b>{pkg['nama']}</b>\n\n"
            f"Ketik nama baru:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
            ])
        )

    elif query.data.startswith("owner_vip_edit_harga_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_edit_harga_", ""))
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_vip_id"] = pkg_id
        context.user_data["waiting_vip_edit_harga"] = True
        harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
        await query.edit_message_text(
            f"💰 <b>Edit Harga Paket</b>\n\n"
            f"Harga saat ini: Rp {harga_fmt}\n\n"
            f"Ketik harga baru (angka):",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
            ])
        )

    elif query.data.startswith("owner_vip_edit_group_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_edit_group_", ""))
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_vip_id"] = pkg_id
        context.user_data["waiting_vip_edit_group"] = True
        current = pkg.get("group_id") or pkg.get("link") or "-"
        teks = (
            f"🔗 <b>Edit Group ID / Link</b>\n\n"
            f"Saat ini: <code>{current}</code>\n\n"
            f"Ketik <b>Group ID</b> (angka negatif) atau <b>link=URL</b>:\n\n"
            f"Contoh:\n"
            f"• <code>-1003956111712</code>\n"
            f"• <code>link=https://t.me/+xxx</code>"
        )
        await query.edit_message_text(
            teks,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
            ])
        )

    elif query.data.startswith("owner_vip_edit_banner_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_edit_banner_", ""))
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_vip_id"] = pkg_id
        context.user_data["waiting_vip_edit_banner"] = True
        await query.edit_message_text(
            f"🖼 <b>Edit Banner Paket VIP</b>\n\n"
            f"Paket: <b>{html.escape(pkg['nama'])}</b>\n\n"
            "Kirim foto banner baru untuk paket ini.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
            ])
        )

    elif query.data.startswith("owner_vip_edit_caption_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_edit_caption_", ""))
        pkg = get_package_by_id(pkg_id)
        if not pkg:
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_vip_id"] = pkg_id
        context.user_data["waiting_vip_edit_caption"] = True
        await query.edit_message_text(
            f"📝 <b>Edit Caption Paket VIP</b>\n\n"
            f"Paket: <b>{html.escape(pkg['nama'])}</b>\n\n"
            "Ketik caption baru untuk paket ini.\n\n"
            "<code>{nama}</code> dan <code>{harga}</code> sudah otomatis ditambahkan.\n"
            "Cukup tulis teks body caption.\n"
            "Emoji premium bisa langsung ditempel di caption.\n"
            "Untuk blockquote, gunakan HTML langsung:\n"
            "<code>&lt;blockquote expandable&gt;Benefit paket&lt;/blockquote&gt;</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
            ])
        )

    elif query.data.startswith("owner_vip_preview_caption_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_preview_caption_", ""))
        await _show_vip_caption_preview(query, pkg_id)

    elif query.data.startswith("owner_vip_reset_banner_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_reset_banner_", ""))
        if not clear_package_banner(pkg_id):
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return
        await query.answer("✅ Banner paket direset.", show_alert=True)
        await _show_vip_edit_detail(query, pkg_id)

    elif query.data.startswith("owner_vip_reset_caption_"):
        if not is_owner(query.from_user.id):
            return

        pkg_id = int(query.data.replace("owner_vip_reset_caption_", ""))
        if not clear_package_caption(pkg_id):
            await query.answer("Paket tidak ditemukan.", show_alert=True)
            return
        await query.answer("✅ Caption paket direset.", show_alert=True)
        await _show_vip_edit_detail(query, pkg_id)

    elif query.data == "owner_vs_menu":

        if not is_owner(query.from_user.id):
            return

        from services.admin_service import get_admins

        admins = get_admins()

        teks = "🎥 <b>MANAGE VCS ADMIN</b>\n\n"

        keyboard = []

        for admin in admins:

            status = "🟢 ON" if admin.get("vs_enabled") else "🔴 OFF"

            teks += f"• {admin['nama']} ({admin['id']}) - {status}\n"

            keyboard.append([
                InlineKeyboardButton(
                    f"{admin['nama']} ({status})",
                    callback_data=f"toggle_vs_{admin['id']}"
                )
            ])

        keyboard.append([
            InlineKeyboardButton("⬅️ Kembali", callback_data="owner_adminmenu")
        ])

        await query.edit_message_text(
            teks,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("toggle_vs_"):

        if not is_owner(query.from_user.id):
            return

        user_id = int(query.data.replace("toggle_vs_", ""))

        from services.admin_service import toggle_vs_admin

        toggle_vs_admin(user_id)

        await query.answer("✅ VCS status diupdate!")

        from services.admin_service import get_admins

        admins = get_admins()

        teks = "🎥 <b>MANAGE VCS ADMIN</b>\n\n"

        keyboard = []

        for admin in admins:

            status = (
                "🟢 ON"
                if admin.get("vs_enabled")
                else "🔴 OFF"
            )

            teks += (
                f"• {admin['nama']} "
                f"({admin['id']}) - {status}\n"
            )

            keyboard.append([
                InlineKeyboardButton(
                    f"{admin['nama']} ({status})",
                    callback_data=f"toggle_vs_{admin['id']}"
                )
            ])

        keyboard.append([
            InlineKeyboardButton(
                "⬅️ Kembali",
                callback_data="owner_adminmenu"
            )
        ])

        await query.edit_message_text(
            teks,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "admin_self_toggle":
        if not is_admin(query.from_user.id):
            return

        from services.admin_service import toggle_vs_admin
        toggle_vs_admin(query.from_user.id)

        user = query.from_user
        admin_data = get_admin_by_id(user.id)
        vs_status = admin_data.get("vs_enabled", False) if admin_data else False
        toggle_label = "🔴 OFF kan Diri Saya" if vs_status else "🟢 ON kan Diri Saya"
        status_teks = "🟢 ON" if vs_status else "🔴 OFF"
        nama_admin = admin_data["nama"] if admin_data else user.first_name
        username = f"@{user.username}" if user.username else "Tidak disetel"

        await query.answer(f"Status VS kamu sekarang: {status_teks}", show_alert=True)

        await query.edit_message_text(
            f"🎖️ <b>ADMIN PANEL - {nama_admin}</b>\n"
            f"👤 <b>Nama Admin:</b> {nama_admin}\n"
            f"👤 <b>Nama:</b> {user.first_name}\n"
            f"📌 <b>Username:</b> {username}\n"
            f"🆔 <b>ID:</b> <code>{user.id}</code>\n\n"
            f"Status VS: {status_teks}",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("🎥 Kelola Paket VCS", callback_data="vs_manage")],
                [InlineKeyboardButton(toggle_label, callback_data="admin_self_toggle")]
            ])
        )

    # =========================
    # KELOLA PAYMENT (OWNER)
    # =========================

    elif query.data == "owner_payment_menu":
        if not is_owner(query.from_user.id):
            return

        info = get_payment_info()
        qris_status = "✅ Sudah diset" if info.get("qris_file_id") else "❌ Belum diset"
        dana_status = f"✅ {info['dana_number']}" if info.get("dana_number") else "❌ Belum diset"

        await query.edit_message_text(
            "💳 <b>KELOLA PAYMENT</b>\n\n"
            f"🖼 QRIS   : {qris_status}\n"
            f"💳 Dana  : {dana_status}",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("🖼 Set QRIS", callback_data="owner_set_qris")],
                [InlineKeyboardButton("💳 Set Nomor Dana", callback_data="owner_set_dana")],
                [InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")],
            ])
        )

    elif query.data == "owner_set_qris":
        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_qris"] = True

        await query.edit_message_text(
            "🖼 <b>Upload Gambar QRIS</b>\n\n"
            "Kirim foto QRIS baru sekarang.\n"
            "Foto tersebut akan langsung disimpan dan digunakan untuk semua transaksi.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data="owner_payment_menu")]
            ])
        )

    elif query.data == "owner_set_dana":
        if not is_owner(query.from_user.id):
            return

        context.user_data["waiting_dana"] = True

        await query.edit_message_text(
            "💳 <b>Set Nomor Dana</b>\n\n"
            "Ketik nomor Dana kamu sekarang.\n"
            "Contoh: <code>08123456789</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data="owner_payment_menu")]
            ])
        )

    elif query.data == "owner_set_banner":
        if not is_owner(query.from_user.id):
            return

        from services.payment_info_service import get_banner_file_id
        status = "✅ Sudah diset" if get_banner_file_id() else "❌ Belum diset (pakai file lokal)"
        context.user_data["waiting_banner"] = True

        await query.edit_message_text(
            f"🖼 <b>Ganti Banner</b>\n\n"
            f"Status saat ini: {status}\n\n"
            "Kirim foto banner baru sekarang.\n"
            "Foto tersebut akan langsung digunakan saat user membuka bot.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data="panel_admin")]
            ])
        )

    # =========================
    # KELOLA CHANNEL (OWNER)
    # =========================

    elif query.data == "owner_channel_menu":
        if not is_owner(query.from_user.id):
            return

        for key in ["waiting_channel_name", "waiting_channel_link", "waiting_channel_chat_id",
                     "waiting_channel_edit_name", "waiting_channel_edit_link", "waiting_channel_edit_chatid",
                     "channel_new_name", "editing_channel_id"]:
            context.user_data.pop(key, None)

        keyboard = [
            [InlineKeyboardButton("📝 Kelola Channel Testi", callback_data="owner_channel_testi_list")],
            [InlineKeyboardButton("🔗 Kelola Channel Terhubung", callback_data="owner_channel_terhubung")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_settings")],
        ]

        await query.edit_message_text(
            "📢 <b>Kelola Channel</b>\n\nPilih menu:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_channel_testi_list":
        if not is_owner(query.from_user.id):
            return

        channels = get_all_channels()
        keyboard = []
        for ch in channels:
            icon = "🔓" if ch["type"] == "public" else "🔒"
            identifier = ch.get("username") or ch.get("link") or str(ch.get("chat_id") or "-")
            keyboard.append([
                InlineKeyboardButton(
                    f"{icon} {ch['name']} ({identifier})",
                    callback_data=f"owner_channel_detail_{ch['id']}"
                )
            ])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_menu")])

        await query.edit_message_text(
            "📢 <b>Kelola Channel Testi</b>\n\nDaftar channel testi yang terhubung dengan bot:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_channel_terhubung":
        if not is_owner(query.from_user.id):
            return

        channels = get_all_connected_channels()
        keyboard = []
        for ch in channels:
            icon = "🔓" if ch["type"] == "public" else "🔒"
            identifier = ch.get("username") or ch.get("link") or str(ch.get("chat_id") or "-")
            status_icon = "🟢" if ch.get("status") == "active" else "🔴" if ch.get("status") == "inactive" else "⚪"
            keyboard.append([
                InlineKeyboardButton(
                    f"{icon} {ch['name']} ({identifier}) {status_icon}",
                    callback_data=f"owner_conn_channel_detail_{ch['id']}"
                )
            ])
        keyboard.append([InlineKeyboardButton("➕ Tambah Channel Terhubung", callback_data="owner_conn_channel_add")])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_menu")])

        await query.edit_message_text(
            "📢 <b>Kelola Channel Terhubung</b>\n\nDaftar channel yang terhubung dengan bot:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data == "owner_conn_channel_add":
        if not is_owner(query.from_user.id):
            return

        context.user_data["conn_waiting_name"] = True
        await query.edit_message_text(
            "🔗 <b>Tambah Channel Terhubung</b>\n\n"
            "Ketik **nama** channel:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("❌ Batal", callback_data="owner_channel_terhubung")]
            ])
        )

    elif query.data == "owner_conn_channel_add_public":
        if not is_owner(query.from_user.id):
            return

        context.user_data["conn_type"] = "public"
        context.user_data["conn_waiting_link"] = True
        await query.edit_message_text(
            "🔓 <b>Tambah Channel Public</b>\n\n"
            "Kirim **link** atau **@username** channel:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data="owner_channel_terhubung")]
            ])
        )

    elif query.data == "owner_conn_channel_add_private":
        if not is_owner(query.from_user.id):
            return

        context.user_data["conn_type"] = "private"
        context.user_data["conn_waiting_chat_id"] = True
        await query.edit_message_text(
            "🔒 <b>Tambah Channel Private</b>\n\n"
            "Kirim **Chat ID** channel (angka negatif, contoh: -1001234567890):",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data="owner_channel_terhubung")]
            ])
        )

    elif query.data.startswith("owner_conn_channel_detail_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_conn_channel_detail_", ""))
        ch = get_connected_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            await _edit_query_text(query, "⚠️ Channel tidak ditemukan.")
            return

        icon = "🔓" if ch["type"] == "public" else "🔒"
        identifier = ch.get("username") or ch.get("link") or "-"
        chat_id_str = str(ch.get("chat_id") or "-")
        status_text = {
            "active": "🟢 Aktif",
            "inactive": "🔴 Tidak Aktif",
            "unknown": "⚪ Belum Dicek",
        }.get(ch.get("status", "unknown"), "⚪ Belum Dicek")

        text = (
            f"{icon} <b>{ch['name']}</b>\n\n"
            f"Tipe: <b>{ch['type'].title()}</b>\n"
        )
        if ch["type"] == "public":
            text += f"Link: <code>{identifier}</code>\n"
        else:
            text += f"Chat ID: <code>{chat_id_str}</code>\n"
        text += f"Status: {status_text}\n"
        try:
            chat_info = await get_connected_channel_chat(context, ch)
            if not ch.get("chat_id") or ch["chat_id"] != chat_info.id:
                ch = update_connected_channel(ch["id"], chat_id=chat_info.id) or ch
            text += f"🆔 ID Telegram: <code>{chat_info.id}</code>\n"
            text += f"📋 Tipe Telegram: <b>{chat_info.type.title()}</b>\n"
            member_count = await get_connected_channel_member_count(context, chat_info.id)
            text += f"👥 Total Member Telegram: <b>{member_count}</b>\n"
        except Exception:
            text += "👥 Total Member Telegram: <i>Gagal dibaca (pastikan bot admin)</i>\n"
        if ch.get("last_checked"):
            text += f"Terakhir cek: {ch['last_checked']}\n"
        text += f"\nDibuat: {ch.get('created_at', '-')}"

        keyboard = [
            [InlineKeyboardButton("🔄 Cek Koneksi", callback_data=f"owner_conn_channel_cek_{ch['id']}")],
            [InlineKeyboardButton("👥 Data User", callback_data=f"owner_conn_channel_users_{ch['id']}")],
            [InlineKeyboardButton("🗑 Hapus Channel", callback_data=f"owner_conn_channel_delete_{ch['id']}")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_terhubung")],
        ]

        await query.edit_message_text(
            text,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("owner_conn_channel_cek_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_conn_channel_cek_", ""))
        ch = get_connected_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        await query.answer("🔄 Mengecek koneksi...")
        success, msg = await check_channel_connection(context, ch)
        ch = get_connected_channel_by_id(ch_id)
        now = datetime.now().strftime("%H:%M:%S")

        icon = "🔓" if ch["type"] == "public" else "🔒"
        identifier = ch.get("username") or ch.get("link") or "-"
        chat_id_str = str(ch.get("chat_id") or "-")
        status_text = {
            "active": "🟢 Aktif",
            "inactive": "🔴 Tidak Aktif",
            "unknown": "⚪ Belum Dicek",
        }.get(ch.get("status", "unknown"), "⚪ Belum Dicek")

        text = (
            f"{icon} <b>{ch['name']}</b>\n\n"
            f"Tipe: <b>{ch['type'].title()}</b>\n"
        )
        if ch["type"] == "public":
            text += f"Link: <code>{identifier}</code>\n"
        else:
            text += f"Chat ID: <code>{chat_id_str}</code>\n"
        text += f"Status: {status_text}\n"
        if ch.get("last_checked"):
            text += f"Terakhir cek: {ch['last_checked']}\n"
        text += f"\nHasil Cek: {msg}\n"
        text += f"⏱ Dicek pukul: {now}\n"
        text += f"\nDibuat: {ch.get('created_at', '-')}"

        keyboard = [
            [InlineKeyboardButton("🔄 Cek Ulang", callback_data=f"owner_conn_channel_cek_{ch['id']}")],
            [InlineKeyboardButton("👥 Data User", callback_data=f"owner_conn_channel_users_{ch['id']}")],
            [InlineKeyboardButton("🗑 Hapus Channel", callback_data=f"owner_conn_channel_delete_{ch['id']}")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_terhubung")],
        ]

        await query.edit_message_text(
            text,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("owner_conn_channel_users_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_conn_channel_users_", ""))
        ch = get_connected_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        from db.database import cursor as db_cursor

        text = f"👥 <b>Data User - {ch['name']}</b>\n\n"
        resolved_chat_id = None

        try:
            chat_info = await get_connected_channel_chat(context, ch)
            resolved_chat_id = chat_info.id
            title = chat_info.title or ch["name"]
            text += f"📌 Nama: <b>{title}</b>\n"
            text += f"🆔 ID: <code>{chat_info.id}</code>\n"
            text += f"📋 Tipe: <b>{chat_info.type.title()}</b>\n"
            try:
                member_count = await get_connected_channel_member_count(context, chat_info.id)
                text += f"👥 Total Member Telegram: <b>{member_count}</b>\n"
            except Exception:
                text += "👥 Total Member Telegram: <i>Gagal dibaca (pastikan bot admin)</i>\n"
            if chat_info.type in ("supergroup", "group", "channel"):
                try:
                    admins = await chat_info.get_administrators()
                    text += f"👤 Admin: <b>{len(admins)} orang</b>\n"
                except Exception:
                    pass
            if not ch.get("chat_id") or ch["chat_id"] != chat_info.id:
                update_connected_channel(ch["id"], chat_id=chat_info.id)
                ch["chat_id"] = chat_info.id
        except Exception as e:
            text += f"⚠️ Gagal ambil info chat: {str(e)[:50]}\n"

        lookup_id = resolved_chat_id or ch.get("chat_id")
        if lookup_id:
            db_cursor.execute(
                "SELECT COUNT(*) as cnt FROM member_links WHERE chat_id = ? AND status = 'Aktif'",
                (lookup_id,)
            )
            row = db_cursor.fetchone()
            link_count = row["cnt"] if row else 0
            if link_count > 0:
                text += f"\n🔗 User dengan akses link aktif (data bot): <b>{link_count}</b>\n"
                text += "<i>Angka ini bukan total member Telegram.</i>\n\n"
                db_cursor.execute(
                    "SELECT user_id, group_name, join_date FROM member_links WHERE chat_id = ? AND status = 'Aktif' ORDER BY join_date DESC LIMIT 10",
                    (lookup_id,)
                )
                rows = db_cursor.fetchall()
                for r in rows:
                    text += f"├ 👤 <code>{r['user_id']}</code> — {r['group_name']} ({r['join_date']})\n"
                if link_count > 10:
                    text += f"└ ... dan {link_count - 10} lainnya\n"
            else:
                text += "\n📭 Belum ada data user dari link undangan.\n"

        keyboard = [
            [InlineKeyboardButton("⬅️ Kembali", callback_data=f"owner_conn_channel_detail_{ch_id}")],
        ]

        await query.edit_message_text(
            text,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("owner_conn_channel_delete_confirm_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_conn_channel_delete_confirm_", ""))
        ch = get_connected_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        delete_connected_channel(ch_id)
        await query.answer(f"✅ Channel '{ch['name']}' berhasil dihapus.", show_alert=True)

        channels = get_all_connected_channels()
        keyboard = []
        for ch_ in channels:
            icon = "🔓" if ch_["type"] == "public" else "🔒"
            identifier = ch_.get("username") or ch_.get("link") or str(ch_.get("chat_id") or "-")
            status_icon = "🟢" if ch_.get("status") == "active" else "🔴" if ch_.get("status") == "inactive" else "⚪"
            keyboard.append([
                InlineKeyboardButton(
                    f"{icon} {ch_['name']} ({identifier}) {status_icon}",
                    callback_data=f"owner_conn_channel_detail_{ch_['id']}"
                )
            ])
        keyboard.append([InlineKeyboardButton("➕ Tambah Channel Terhubung", callback_data="owner_conn_channel_add")])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_terhubung")])

        await query.edit_message_text(
            "📢 <b>Kelola Channel Terhubung</b>\n\nDaftar channel yang terhubung dengan bot:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("owner_conn_channel_delete_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_conn_channel_delete_", ""))
        ch = get_connected_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        await query.edit_message_text(
            f"🗑 <b>Hapus Channel</b>\n\n"
            f"Yakin ingin menghapus <b>{ch['name']}</b>?",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [
                    InlineKeyboardButton("✅ Ya, Hapus", callback_data=f"owner_conn_channel_delete_confirm_{ch_id}"),
                    InlineKeyboardButton("❌ Batal", callback_data=f"owner_conn_channel_detail_{ch_id}")
                ]
            ])
        )

    elif query.data.startswith("owner_channel_detail_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_channel_detail_", ""))
        ch = get_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            await _edit_query_text(query, "⚠️ Channel tidak ditemukan.")
            return

        icon = "🔓" if ch["type"] == "public" else "🔒"
        identifier = ch.get("username") or ch.get("link") or "-"
        chat_id_str = str(ch.get("chat_id") or "-")

        text = (
            f"{icon} <b>{ch['name']}</b>\n\n"
            f"Tipe: <b>{ch['type'].title()}</b>\n"
        )
        if ch["type"] == "public":
            text += f"Link: <code>{identifier}</code>\n"
        else:
            text += f"Chat ID: <code>{chat_id_str}</code>\n"

        text += f"\nDibuat: {ch.get('created_at', '-')}"

        keyboard = [
            [InlineKeyboardButton("✏️ Edit Nama", callback_data=f"owner_channel_edit_name_{ch['id']}")]
        ]
        if ch["type"] == "public":
            keyboard.append([InlineKeyboardButton("🔗 Edit Link", callback_data=f"owner_channel_edit_link_{ch['id']}")])
        else:
            keyboard.append([InlineKeyboardButton("🆔 Edit Chat ID", callback_data=f"owner_channel_edit_chatid_{ch['id']}")])
        keyboard.append([InlineKeyboardButton("🗑 Hapus Channel", callback_data=f"owner_channel_delete_{ch['id']}")])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_testi_list")])

        await query.edit_message_text(
            text,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("owner_channel_edit_name_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_channel_edit_name_", ""))
        ch = get_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_channel_id"] = ch_id
        context.user_data["waiting_channel_edit_name"] = True
        await query.edit_message_text(
            f"✏️ <b>Edit Nama Channel</b>\n\n"
            f"Nama saat ini: <b>{ch['name']}</b>\n\n"
            f"Ketik nama baru:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_channel_detail_{ch_id}")]
            ])
        )

    elif query.data.startswith("owner_channel_edit_link_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_channel_edit_link_", ""))
        ch = get_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_channel_id"] = ch_id
        context.user_data["waiting_channel_edit_link"] = True
        identifier = ch.get("username") or ch.get("link") or "-"
        await query.edit_message_text(
            f"🔗 <b>Edit Link Channel</b>\n\n"
            f"Link saat ini: <code>{identifier}</code>\n\n"
            f"Kirim link atau @username baru:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_channel_detail_{ch_id}")]
            ])
        )

    elif query.data.startswith("owner_channel_edit_chatid_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_channel_edit_chatid_", ""))
        ch = get_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        context.user_data["editing_channel_id"] = ch_id
        context.user_data["waiting_channel_edit_chatid"] = True
        await query.edit_message_text(
            f"🆔 <b>Edit Chat ID Channel</b>\n\n"
            f"Chat ID saat ini: <code>{ch.get('chat_id', '-')}</code>\n\n"
            f"Kirim Chat ID baru (angka negatif):",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data=f"owner_channel_detail_{ch_id}")]
            ])
        )

    elif query.data.startswith("owner_channel_delete_confirm_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_channel_delete_confirm_", ""))
        ch = get_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        delete_channel(ch_id)
        await query.answer(f"✅ Channel '{ch['name']}' berhasil dihapus.", show_alert=True)

        channels = get_all_channels()
        keyboard = []
        for ch_ in channels:
            icon = "🔓" if ch_["type"] == "public" else "🔒"
            identifier = ch_.get("username") or ch_.get("link") or str(ch_.get("chat_id") or "-")
            keyboard.append([
                InlineKeyboardButton(
                    f"{icon} {ch_['name']} ({identifier})",
                    callback_data=f"owner_channel_detail_{ch_['id']}"
                )
            ])
        keyboard.append([InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_testi_list")])

        await query.edit_message_text(
            "📢 <b>Kelola Channel Testi</b>\n\nDaftar channel testi yang terhubung dengan bot:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    elif query.data.startswith("owner_channel_delete_"):
        if not is_owner(query.from_user.id):
            return

        ch_id = int(query.data.replace("owner_channel_delete_", ""))
        ch = get_channel_by_id(ch_id)
        if not ch:
            await query.answer("Channel tidak ditemukan.", show_alert=True)
            return

        await query.edit_message_text(
            f"🗑 <b>Hapus Channel</b>\n\n"
            f"Yakin ingin menghapus <b>{ch['name']}</b>?",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [
                    InlineKeyboardButton("✅ Ya, Hapus", callback_data=f"owner_channel_delete_confirm_{ch_id}"),
                    InlineKeyboardButton("❌ Batal", callback_data=f"owner_channel_detail_{ch_id}")
                ]
            ])
        )

    # =========================
    # PILIH METODE PEMBAYARAN (USER)
    # =========================

    elif query.data == "pay_method_qris":
        context.user_data["payment_method"] = "QRIS"
        caption = _build_payment_invoice(
            context,
            query.from_user.id,
            "QRIS"
        ) + "\n\n📸 Scan QRIS di bawah, lalu kirim foto bukti transfer."
        keyboard = [[InlineKeyboardButton("❌ Batalkan Transaksi", callback_data="batal_transaksi")]]
        markup = InlineKeyboardMarkup(keyboard)

        qris_file_id = get_qris_file_id()
        has_static = os.path.exists(QRIS_PATH)

        if not qris_file_id and not has_static:
            sent = await _replace_query_with_text(query, context, caption, reply_markup=markup)
            context.user_data["qris_msg_id"] = sent.message_id
            return

        if visible_text_length(caption) > TELEGRAM_CAPTION_LIMIT:
            media = qris_file_id if qris_file_id else open(QRIS_PATH, "rb")
            short_caption = "📸 Scan QRIS di gambar ini, lalu kirim foto bukti transfer."
            qris_msg_ids = []
            try:
                await query.edit_message_media(
                    media=InputMediaPhoto(media=media, caption=short_caption),
                    reply_markup=None
                )
                qris_msg_ids.append(query.message.message_id)
            except Exception:
                sent_photo = await context.bot.send_photo(
                    chat_id=query.from_user.id,
                    photo=qris_file_id if qris_file_id else open(QRIS_PATH, "rb"),
                    caption=short_caption
                )
                qris_msg_ids.append(sent_photo.message_id)

            sent_text = await context.bot.send_message(
                chat_id=query.from_user.id,
                text=caption,
                parse_mode="HTML",
                reply_markup=markup
            )
            qris_msg_ids.append(sent_text.message_id)
            context.user_data["qris_msg_ids"] = qris_msg_ids
            return

        media = qris_file_id if qris_file_id else open(QRIS_PATH, "rb")
        try:
            await query.edit_message_media(
                media=InputMediaPhoto(media=media, caption=caption, parse_mode="HTML"),
                reply_markup=markup
            )
            context.user_data["qris_msg_id"] = query.message.message_id
        except Exception:
            sent = await context.bot.send_photo(
                chat_id=query.from_user.id,
                photo=qris_file_id if qris_file_id else open(QRIS_PATH, "rb"),
                caption=caption,
                parse_mode="HTML",
                reply_markup=markup
            )
            context.user_data["qris_msg_id"] = sent.message_id

    elif query.data == "pay_method_dana":
        context.user_data["payment_method"] = "Dana"
        dana = get_payment_info().get("dana_number", "-")
        caption = (
            _build_payment_invoice(
                context,
                query.from_user.id,
                "Dana"
            ) +
            f"\n\n💳 Transfer ke Dana:\n<code>{dana}</code>\n\n"
            f"📸 Kirim foto bukti setelah transfer."
        )
        keyboard = [[InlineKeyboardButton("❌ Batalkan Transaksi", callback_data="batal_transaksi")]]
        markup = InlineKeyboardMarkup(keyboard)

        if visible_text_length(caption) > TELEGRAM_CAPTION_LIMIT:
            sent = await _replace_query_with_text(query, context, caption, reply_markup=markup)
            context.user_data["qris_msg_id"] = sent.message_id
            return

        try:
            await query.edit_message_caption(
                caption=caption,
                parse_mode="HTML",
                reply_markup=markup
            )
        except Exception:
            await query.edit_message_text(
                caption,
                parse_mode="HTML",
                reply_markup=markup
            )
        context.user_data["qris_msg_id"] = query.message.message_id
