import logging
import os
import re
import html

from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes

from services.promo_service import get_promo, use_promo
from services.auth_service import is_admin, is_owner
from services.vs_service import set_vs_caption
from services.maintenance_service import get_maintenance
from config import QRIS_PATH, BANNER_PATH
from config import OWNERS
from services.payment_info_service import get_payment_info
from services.contact_service import (
    create_contact_message,
    get_contact_message,
    mark_contact_replied,
)
from services.channel_service import update_channel
from services.connected_channel_service import add_connected_channel
from services.vip_service import update_package, get_package_by_id, set_package_caption, is_package_visible
from utils.custom_emoji import (
    DANA_BUTTON_ICON_ID,
    QRIS_BUTTON_ICON_ID,
    premium_button_icon_kwargs,
    text_with_custom_emoji_html,
    visible_text_length,
)
from utils.admin_transaction_report import format_user_transaction_report


CUSTOM_CAPTION_LIMIT = 3900


def _convert_quote_to_blockquote(text):
    return re.sub(
        r'"([^"]*)"',
        r'<blockquote expandable>\1</blockquote>',
        text,
    )

log = logging.getLogger("bot.text")


def _format_rupiah(value):
    try:
        return f"Rp {int(value):,}".replace(",", ".")
    except (TypeError, ValueError):
        return "Rp 0"


async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.effective_user
    text = update.message.text

    if get_maintenance() and not is_owner(user.id):
        await update.message.reply_text(
            "🔧 <b>Bot sedang dalam maintenance.</b>\n\n"
            "Mohon bersabar, bot akan segera aktif kembali. Terima kasih! 🙏",
            parse_mode="HTML"
        )
        return

    # Jika admin sedang dalam alur broadcast, teruskan ke handler broadcast
    if context.user_data.get("broadcast_step"):
        from handlers.broadcast_handler import handle_broadcast_text
        await handle_broadcast_text(update, context)
        return

    # OWNER: BALAS PESAN CONTACT ADMIN
    if is_owner(user.id) and context.user_data.get("waiting_contact_reply"):
        message_id = context.user_data.pop("waiting_contact_reply")
        await _handle_contact_reply(update, context, text, message_id)
        return

    # OWNER: CARI TRANSAKSI USER DI LAPORAN
    if is_owner(user.id) and context.user_data.get("waiting_owner_report_search"):
        context.user_data.pop("waiting_owner_report_search", None)
        await update.message.reply_text(
            format_user_transaction_report(text),
            parse_mode="HTML",
            disable_web_page_preview=True,
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("⬅️ Kembali", callback_data="owner_transaction_report")
            ]])
        )
        return

    # USER: KIRIM PESAN CONTACT ADMIN
    if context.user_data.get("waiting_contact_message"):
        context.user_data.pop("waiting_contact_message", None)
        topic = context.user_data.pop("contact_topic", "Lainnya")
        await _handle_contact_message(update, context, text, topic)
        return

    # OWNER: WIZARD TAMBAH PAKET VIP
    if is_owner(user.id) and context.user_data.get("waiting_vip_add_name"):
        await _handle_vip_add_name(update, context, text)
        return

    if is_owner(user.id) and context.user_data.get("waiting_vip_add_price"):
        await _handle_vip_add_price(update, context, text)
        return

    if is_owner(user.id) and context.user_data.get("waiting_vip_add_group"):
        await _handle_vip_add_group(update, context, text)
        return

    if is_owner(user.id) and context.user_data.get("waiting_vip_add_link"):
        await _handle_vip_add_link(update, context, text)
        return

    # OWNER: TAMBAH PAKET VIP
    if is_owner(user.id) and context.user_data.get("waiting_vip_add"):
        context.user_data.pop("waiting_vip_add", None)
        await _handle_vip_add(update, context, text)
        return

    # OWNER: BUAT PROMO
    if is_owner(user.id) and context.user_data.get("waiting_promo_add"):
        context.user_data.pop("waiting_promo_add", None)
        await _handle_promo_add(update, context, text)
        return

    # OWNER: HAPUS LINK
    if is_owner(user.id) and context.user_data.get("waiting_hapus_link"):
        context.user_data.pop("waiting_hapus_link", None)
        await _handle_hapus_link(update, context, text)
        return

    # OWNER: EDIT LINK
    if is_owner(user.id) and context.user_data.get("waiting_edit_link"):
        context.user_data.pop("waiting_edit_link", None)
        await _handle_edit_link(update, context, text)
        return

    # OWNER: TAMBAH ADMIN
    if is_owner(user.id) and context.user_data.get("waiting_add_admin"):
        context.user_data.pop("waiting_add_admin", None)
        await _handle_add_admin(update, context, text)
        return

    # OWNER: HAPUS ADMIN
    if is_owner(user.id) and context.user_data.get("waiting_remove_admin"):
        context.user_data.pop("waiting_remove_admin", None)
        await _handle_remove_admin(update, context, text)
        return

    # ADMIN: TAMBAH PAKET VCS
    if is_admin(user.id) and context.user_data.get("waiting_vs_add"):
        context.user_data.pop("waiting_vs_add", None)
        await _handle_vs_add(update, context, text)
        return

    # OWNER: TAMBAH USER MANUAL KE MEMBER LINKS
    if is_owner(user.id) and context.user_data.get("waiting_manual_add"):
        pkg_id = context.user_data.pop("waiting_manual_add")
        await _handle_manual_add(update, context, text, pkg_id)
        return

    # OWNER: HAPUS USER MANUAL - INPUT USER ID
    if is_owner(user.id) and context.user_data.get("waiting_hapus_manual"):
        pkg_id = context.user_data.pop("waiting_hapus_manual")
        await _handle_hapus_manual_input(update, context, text, pkg_id)
        return

    # ADMIN: CARI MEMBER PAKET
    if is_admin(user.id) and context.user_data.get("waiting_member_search"):
        pkg_id = context.user_data.pop("waiting_member_search")
        await _handle_member_search(update, context, text, pkg_id)
        return

    # ADMIN: CEK USER
    if is_admin(user.id) and context.user_data.get("waiting_cek_user"):
        context.user_data.pop("waiting_cek_user", None)
        try:
            user_id = int(text.strip())
        except ValueError:
            await update.message.reply_text(
                "❌ USER_ID harus berupa angka.",
                reply_markup=InlineKeyboardMarkup([[
                    InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_info")
                ]])
            )
            return
        from handlers.admin_handler import _do_cek_user
        await _do_cek_user(update, context, user_id)
        return

    # ADMIN REPLY ke user
    if is_admin(user.id) and update.message.reply_to_message:
        replied = update.message.reply_to_message
        match = re.search(r"ID: (\d+)", replied.caption or "")
        if match:
            user_id = int(match.group(1))
            await context.bot.send_message(
                chat_id=user_id,
                text=f"📢 Pesan dari Admin:\n\n{text}"
            )
            await update.message.reply_text(
                "✅ Balasan terkirim ke user.",
                reply_markup=InlineKeyboardMarkup([[
                    InlineKeyboardButton("⬅️ Kembali", callback_data="panel_admin")
                ]])
            )
        return

    # OWNER SET NOMOR DANA
    if is_admin(user.id) and context.user_data.get("waiting_dana"):
        from services.payment_info_service import set_dana as save_dana
        save_dana(text.strip())
        context.user_data.pop("waiting_dana", None)
        await update.message.reply_text(
            f"✅ Nomor Dana berhasil disimpan: <code>{text.strip()}</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("⬅️ Kembali", callback_data="owner_payment_menu")
            ]])
        )
        return

    # OWNER SET CAPTION MENU UTAMA
    if is_owner(user.id) and context.user_data.get("waiting_main_caption"):
        from services.payment_info_service import set_main_caption
        caption = text_with_custom_emoji_html(update.message).strip()
        if not caption:
            await update.message.reply_text("❌ Caption tidak boleh kosong.")
            return
        if visible_text_length(caption) > CUSTOM_CAPTION_LIMIT:
            await update.message.reply_text(
                f"❌ Caption terlalu panjang. Maksimal {CUSTOM_CAPTION_LIMIT} karakter agar tetap aman dikirim Telegram."
            )
            return
        set_main_caption(caption)
        context.user_data.pop("waiting_main_caption", None)
        await update.message.reply_text(
            "✅ Caption menu utama berhasil diperbarui.",
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("📝 Kelola Caption", callback_data="owner_caption_menu")
            ]])
        )
        return

    # ADMIN UBAH CAPTION VS
    if is_admin(user.id) and context.user_data.get("vs_waiting_caption"):
        target_admin = context.user_data.get("vs_target_admin", user.id)
        caption = text_with_custom_emoji_html(update.message).strip()
        set_vs_caption(target_admin, caption)
        context.user_data.pop("vs_waiting_caption", None)
        await update.message.reply_text(
            "✅ Caption katalog VS berhasil diperbarui.",
            reply_markup=InlineKeyboardMarkup([[
                InlineKeyboardButton("⬅️ Kembali", callback_data="vs_manage")
            ]])
        )
        return

    # OWNER: CONNECTED CHANNEL - TAMBAH NAMA
    if is_owner(user.id) and context.user_data.get("conn_waiting_name"):
        context.user_data.pop("conn_waiting_name", None)
        context.user_data["conn_new_name"] = text.strip()
        await update.message.reply_text(
            "🔗 <b>Tambah Channel Terhubung</b>\n\n"
            f"Nama: <b>{text.strip()}</b>\n\n"
            "Pilih tipe channel:",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("🔓 Public", callback_data="owner_conn_channel_add_public")],
                [InlineKeyboardButton("🔒 Private", callback_data="owner_conn_channel_add_private")],
                [InlineKeyboardButton("⬅️ Batal", callback_data="owner_channel_terhubung")]
            ])
        )
        return

    # OWNER: CONNECTED CHANNEL - TAMBAH LINK (PUBLIC)
    if is_owner(user.id) and context.user_data.get("conn_waiting_link"):
        context.user_data.pop("conn_waiting_link", None)
        context.user_data["conn_new_link"] = text.strip()
        context.user_data["conn_waiting_chat_id"] = True
        await update.message.reply_text(
            "🔓 <b>Tambah Channel Public</b>\n\n"
            f"Link: <code>{text.strip()}</code>\n\n"
            "Sekarang kirim **Chat ID** channel (angka negatif, contoh: -1001234567890):",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Batal", callback_data="owner_channel_terhubung")]
            ])
        )
        return

    # OWNER: CONNECTED CHANNEL - TAMBAH CHAT ID
    if is_owner(user.id) and context.user_data.get("conn_waiting_chat_id"):
        context.user_data.pop("conn_waiting_chat_id", None)
        name = context.user_data.pop("conn_new_name", "Channel")
        type_ = context.user_data.pop("conn_type", "private")
        link_input = context.user_data.pop("conn_new_link", None)
        try:
            chat_id = int(text.strip())
        except ValueError:
            await update.message.reply_text(
                "❌ Chat ID harus berupa angka.\n\n"
                "Contoh: <code>-1001234567890</code>",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("⬅️ Kembali", callback_data="owner_channel_terhubung")]
                ])
            )
            return

        username = None
        link = None
        if link_input:
            if link_input.startswith("@"):
                username = link_input
                link = f"https://t.me/{link_input.replace('@', '')}"
            elif "t.me/" in link_input:
                link = link_input
                username = "@" + link_input.rstrip("/").split("/")[-1]
            else:
                link = link_input

        ch = add_connected_channel(name, type_, chat_id=chat_id, username=username, link=link)
        type_icon = "🔓" if type_ == "public" else "🔒"
        await update.message.reply_text(
            f"✅ {type_icon} Channel <b>{ch['name']}</b> berhasil ditambahkan!\n\n"
            f"🆔 Chat ID: <code>{chat_id}</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("🔗 Kelola Channel Terhubung", callback_data="owner_channel_terhubung")]
            ])
        )
        return

    # OWNER: CHANNEL - EDIT NAMA
    if is_owner(user.id) and context.user_data.get("waiting_channel_edit_name"):
        context.user_data.pop("waiting_channel_edit_name", None)
        ch_id = context.user_data.pop("editing_channel_id", None)
        if not ch_id:
            return
        ch = update_channel(ch_id, name=text.strip())
        await update.message.reply_text(
            f"✅ Nama channel berhasil diubah menjadi <b>{ch['name']}</b>.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("📢 Detail Channel", callback_data=f"owner_channel_detail_{ch_id}")]
            ])
        )
        return

    # OWNER: CHANNEL - EDIT LINK (PUBLIC)
    if is_owner(user.id) and context.user_data.get("waiting_channel_edit_link"):
        context.user_data.pop("waiting_channel_edit_link", None)
        ch_id = context.user_data.pop("editing_channel_id", None)
        if not ch_id:
            return
        link_input = text.strip()
        username = None
        link = None
        if link_input.startswith("@"):
            username = link_input
            link = f"https://t.me/{link_input.replace('@', '')}"
        elif "t.me/" in link_input:
            link = link_input
            username = "@" + link_input.rstrip("/").split("/")[-1]
        else:
            link = link_input
        ch = update_channel(ch_id, username=username, link=link)
        await update.message.reply_text(
            f"✅ Link channel <b>{ch['name']}</b> berhasil diperbarui.\n\n"
            f"🔗 Link: <code>{link}</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("📢 Detail Channel", callback_data=f"owner_channel_detail_{ch_id}")]
            ])
        )
        return

    # OWNER: CHANNEL - EDIT CHAT ID (PRIVATE)
    if is_owner(user.id) and context.user_data.get("waiting_channel_edit_chatid"):
        context.user_data.pop("waiting_channel_edit_chatid", None)
        ch_id = context.user_data.pop("editing_channel_id", None)
        if not ch_id:
            return
        try:
            chat_id = int(text.strip())
        except ValueError:
            await update.message.reply_text(
                "❌ Chat ID harus berupa angka.\n\n"
                "Contoh: <code>-1001234567890</code>",
                parse_mode="HTML"
            )
            return
        ch = update_channel(ch_id, chat_id=chat_id)
        await update.message.reply_text(
            f"✅ Chat ID channel <b>{ch['name']}</b> berhasil diperbarui.\n\n"
            f"🆔 Chat ID: <code>{chat_id}</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("📢 Detail Channel", callback_data=f"owner_channel_detail_{ch_id}")]
            ])
        )
        return

    # OWNER: VIP - EDIT NAMA PAKET
    if is_owner(user.id) and context.user_data.get("waiting_vip_edit_name"):
        context.user_data.pop("waiting_vip_edit_name", None)
        pkg_id = context.user_data.pop("editing_vip_id", None)
        if not pkg_id:
            return
        pkg = update_package(pkg_id, nama=text.strip())
        if not pkg:
            await update.message.reply_text("❌ Paket tidak ditemukan.")
            return
        await update.message.reply_text(
            f"✅ Nama paket berhasil diubah menjadi <b>{pkg['nama']}</b>.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("✏️ Lanjut Edit", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
            ])
        )
        return

    # OWNER: VIP - EDIT HARGA PAKET
    if is_owner(user.id) and context.user_data.get("waiting_vip_edit_harga"):
        context.user_data.pop("waiting_vip_edit_harga", None)
        pkg_id = context.user_data.pop("editing_vip_id", None)
        if not pkg_id:
            return
        try:
            harga = int(text.strip())
        except ValueError:
            await update.message.reply_text(
                "❌ Harga harus berupa angka.",
                parse_mode="HTML"
            )
            return
        pkg = update_package(pkg_id, harga=harga)
        if not pkg:
            await update.message.reply_text("❌ Paket tidak ditemukan.")
            return
        harga_fmt = f"{pkg['harga']:,}".replace(",", ".")
        await update.message.reply_text(
            f"✅ Harga paket berhasil diubah menjadi Rp {harga_fmt}.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("✏️ Lanjut Edit", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
            ])
        )
        return

    # OWNER: VIP - EDIT GROUP ID / LINK
    if is_owner(user.id) and context.user_data.get("waiting_vip_edit_group"):
        context.user_data.pop("waiting_vip_edit_group", None)
        pkg_id = context.user_data.pop("editing_vip_id", None)
        if not pkg_id:
            return
        inp = text.strip()
        if inp.lower().startswith("link="):
            link = inp[5:].strip()
            pkg = update_package(pkg_id, link=link)
            if not pkg:
                await update.message.reply_text("❌ Paket tidak ditemukan.")
                return
            await update.message.reply_text(
                f"✅ Link paket berhasil diperbarui:\n{link}",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("✏️ Lanjut Edit", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
                ])
            )
        else:
            try:
                group_id = int(inp)
                if group_id > 0:
                    group_id = -group_id
            except ValueError:
                await update.message.reply_text(
                    "❌ Format salah. Gunakan angka (Group ID) atau <code>link=URL</code>.",
                    parse_mode="HTML"
                )
                return
            pkg = update_package(pkg_id, group_id=group_id)
            if not pkg:
                await update.message.reply_text("❌ Paket tidak ditemukan.")
                return
            await update.message.reply_text(
                f"✅ Group ID paket berhasil diperbarui:\n<code>{group_id}</code>",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("✏️ Lanjut Edit", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
                ])
            )
        return

    # OWNER: VIP - EDIT CAPTION PAKET
    if is_owner(user.id) and context.user_data.get("waiting_vip_edit_caption"):
        context.user_data.pop("waiting_vip_edit_caption", None)
        pkg_id = context.user_data.pop("editing_vip_id", None)
        if not pkg_id:
            return
        caption = text_with_custom_emoji_html(update.message).strip()
        if not caption:
            await update.message.reply_text("❌ Caption tidak boleh kosong.")
            return
        caption = f"🔥 {{nama}}\n💰 {{harga}}\n\n{caption}"
        if visible_text_length(caption) > CUSTOM_CAPTION_LIMIT:
            await update.message.reply_text(
                f"❌ Caption terlalu panjang. Maksimal {CUSTOM_CAPTION_LIMIT} karakter agar tetap aman dikirim Telegram."
            )
            return
        pkg = set_package_caption(pkg_id, caption)
        if not pkg:
            await update.message.reply_text("❌ Paket tidak ditemukan.")
            return

        if context.user_data.get("vip_add_flow"):
            context.user_data.pop("vip_add_flow", None)
            await update.message.reply_text(
                f"✅ Paket <b>{html.escape(pkg['nama'])}</b> siap digunakan!\n\n"
                "Paket VIP berhasil ditambahkan lengkap dengan banner dan caption.",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([[
                    InlineKeyboardButton("⬅️ Kembali ke Menu", callback_data="owner_vip_menu")
                ]])
            )
        else:
            await update.message.reply_text(
                f"✅ Caption paket <b>{html.escape(pkg['nama'])}</b> berhasil diperbarui.",
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("✏️ Lanjut Edit", callback_data=f"owner_vip_edit_detail_{pkg_id}")]
                ])
            )
        return

    # USER PILIH BEBERAPA PAKET VIP
    if context.user_data.get("vip_choosing_packages"):
        log.info("[TEXT] User %s input pilih paket: %s", user.id, text)
        await _handle_package_selection(update, context, text)
        return

    # USER INPUT PROMO TAKE ALL
    if context.user_data.get("waiting_take_all_promo"):
        if context.user_data.get("paket_type") != "take_all":
            context.user_data.pop("waiting_take_all_promo", None)
            return

        if context.user_data.get("promo_applied"):
            await update.message.reply_text(
                "⚠️ Kamu sudah menggunakan kode promo untuk pesanan ini."
            )
            return

        log.info("[TEXT] User %s input promo take all: %s", user.id, text)
        await _handle_take_all_promo(update, context, text)
        return

    if (
        context.user_data.get("paket_type") == "take_all"
        and not context.user_data.get("take_all_promo_done")
    ):
        await update.message.reply_text(
            "🎟️ Gunakan tombol <b>Masukkan Kode Promo</b> atau <b>Lewati Promo</b> dulu.",
            parse_mode="HTML"
        )


async def _handle_contact_message(update, context, text, topic):
    user = update.effective_user
    message_id = create_contact_message(user, topic, text.strip())
    username = f"@{user.username}" if user.username else "-"

    await update.message.reply_text(
        "✅ <b>Pesan terkirim!</b>\n\nAdmin akan membalas segera.",
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("⬅️ Kembali ke Menu", callback_data="menu")]
        ])
    )

    notif = (
        "📩 <b>Ada pesan baru!</b>\n\n"
        f"👤 {html.escape(user.first_name or 'User')}\n"
        f"📌 {html.escape(username)}\n"
        f"🏷️ Topik: <b>{html.escape(topic)}</b>\n\n"
        "Cek menu Notifications → Pesan Masuk."
    )

    for owner_id in OWNERS:
        try:
            await context.bot.send_message(
                chat_id=owner_id,
                text=notif,
                parse_mode="HTML",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("📩 Buka Pesan", callback_data="owner_notifications")]
                ])
            )
        except Exception:
            log.exception("[CONTACT] Gagal kirim notif ke owner %s", owner_id)


async def _handle_contact_reply(update, context, text, message_id):
    item = get_contact_message(message_id)
    if not item:
        await update.message.reply_text(
            "❌ Pesan tidak ditemukan.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("📩 Pesan Masuk", callback_data="owner_contact_inbox")]
            ])
        )
        return

    await context.bot.send_message(
        chat_id=item["user_id"],
        text=f"📩 <b>Balasan dari Admin:</b>\n\n{html.escape(text.strip())}",
        parse_mode="HTML"
    )
    mark_contact_replied(message_id)

    await update.message.reply_text(
        "✅ Balasan terkirim ke user.",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("📩 Pesan Masuk", callback_data="owner_contact_inbox")]
        ])
    )


async def _handle_package_selection(update, context, text):
    from services.vip_service import get_package_by_id

    try:
        ids = [int(x.strip()) for x in text.split(",") if x.strip()]
    except ValueError:
        log.warning("[PILIH_BEBERAPA] User %s format input salah: %s", update.effective_user.id, text)
        await update.message.reply_text(
            "❌ Format salah. Contoh: <code>1,2</code>",
            parse_mode="HTML"
        )
        return

    if len(ids) < 2:
        log.warning("[PILIH_BEBERAPA] User %s kurang dari 2 paket: %s", update.effective_user.id, ids)
        await update.message.reply_text("⚠️ Minimal pilih 2 paket.")
        return

    if len(set(ids)) != len(ids):
        log.warning("[PILIH_BEBERAPA] User %s ada duplikat ID: %s", update.effective_user.id, ids)
        await update.message.reply_text("⚠️ ID paket tidak boleh duplikat.")
        return

    selected = []
    for pid in ids:
        pkg = get_package_by_id(pid)
        if not pkg:
            log.warning("[PILIH_BEBERAPA] ID paket %s tidak ditemukan.", pid)
            await update.message.reply_text(
                f"❌ ID paket <code>{pid}</code> tidak ditemukan.",
                parse_mode="HTML"
            )
            return
        if not is_package_visible(pkg):
            await update.message.reply_text(
                f"❌ Paket <code>{pid}</code> sedang tidak tersedia.",
                parse_mode="HTML"
            )
            return
        selected.append(pkg)

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

    log.info(
        "[PILIH_BEBERAPA] User %s pilih ids=%s | total=%s | diskon=%s | bayar=%s",
        update.effective_user.id, ids, total, diskon, bayar
    )
    context.user_data["vip_choosing_packages"] = False
    context.user_data["paket_type"] = "take_several"
    context.user_data["vip_selected_ids"] = ids
    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
    context.user_data["transaction_back_view"] = "vip_take_menu"

    total_fmt = f"{total:,}".replace(",", ".")
    diskon_fmt = f"{diskon:,}".replace(",", ".")
    bayar_fmt = f"{bayar:,}".replace(",", ".")

    caption_order = "✅ <b>Paket dipilih:</b>\n"
    for p in selected:
        harga_fmt = f"{p['harga']:,}".replace(",", ".")
        caption_order += f"• {p['nama']} ─ Rp {harga_fmt}\n"
    caption_order += (
        f"\nTotal Normal: Rp {total_fmt}\n"
        f"Diskon 10%: -Rp {diskon_fmt}\n"
        f"💰 <b>Total Bayar: Rp {bayar_fmt}</b>"
    )
    context.user_data["payment_caption"] = caption_order

    info = get_payment_info()
    pay_keyboard = []
    if info.get("qris_file_id") or os.path.exists(QRIS_PATH):
        pay_keyboard.append([InlineKeyboardButton(
            "QRIS",
            callback_data="pay_method_qris",
            **premium_button_icon_kwargs(QRIS_BUTTON_ICON_ID)
        )])
    if info.get("dana_number"):
        pay_keyboard.append([InlineKeyboardButton(
            "Dana",
            callback_data="pay_method_dana",
            **premium_button_icon_kwargs(DANA_BUTTON_ICON_ID)
        )])
    pay_keyboard.append([InlineKeyboardButton("❌ Batalkan", callback_data="batal_transaksi")])

    teks_selection = caption_order + "\n\n💳 <b>Pilih metode pembayaran:</b>"

    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:
        sent = await update.message.reply_photo(
            photo=banner,
            caption=teks_selection,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(pay_keyboard)
        )
    else:
        sent = await update.message.reply_text(
            teks_selection,
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup(pay_keyboard)
        )
    context.user_data["qris_msg_id"] = sent.message_id


async def _finish_vip_add(update, context, group_id=None, link=None):
    from services.vip_service import add_package

    draft = context.user_data.get("vip_add_draft") or {}
    nama = draft.get("nama")
    harga = draft.get("harga")
    if not nama or not harga:
        await update.message.reply_text(
            "❌ Data paket belum lengkap. Mulai ulang dari menu tambah paket.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")]])
        )
        return

    pkg = add_package(nama, harga, group_id=group_id, link=link)
    for key in [
        "vip_add_draft",
        "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["editing_vip_id"] = pkg["id"]
    context.user_data["waiting_vip_edit_banner"] = True
    context.user_data["vip_add_flow"] = True
    await update.message.reply_text(
        f"✅ Paket <b>{html.escape(pkg['nama'])}</b> berhasil ditambahkan!\n\n"
        "Step 5/5\n"
        "Sekarang kirim <b>foto banner</b> untuk paket ini.",
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]])
    )


async def _handle_vip_add_name(update, context, text):
    nama = text.strip()
    if not nama:
        await update.message.reply_text(
            "❌ Nama paket tidak boleh kosong.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]])
        )
        return

    context.user_data["vip_add_draft"] = {"nama": nama}
    context.user_data.pop("waiting_vip_add_name", None)
    context.user_data["waiting_vip_add_price"] = True
    await update.message.reply_text(
        "➕ <b>Tambah Paket VIP</b>\n\n"
        "Step 2/5\n"
        f"Nama: <b>{html.escape(nama)}</b>\n\n"
        "Ketik <b>harga paket</b> dalam angka:\n"
        "Contoh: <code>50000</code>",
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]])
    )


async def _handle_vip_add_price(update, context, text):
    try:
        harga = int(text.strip().replace(".", ""))
    except ValueError:
        await update.message.reply_text(
            "❌ Harga harus berupa angka. Contoh: <code>50000</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]])
        )
        return

    if harga <= 0:
        await update.message.reply_text("❌ Harga harus lebih dari 0.")
        return

    draft = context.user_data.setdefault("vip_add_draft", {})
    draft["harga"] = harga
    context.user_data.pop("waiting_vip_add_price", None)
    harga_fmt = f"{harga:,}".replace(",", ".")
    await update.message.reply_text(
        "➕ <b>Tambah Paket VIP</b>\n\n"
        "Step 3/5\n"
        f"Nama: <b>{html.escape(draft.get('nama', '-'))}</b>\n"
        f"Harga: <b>Rp {harga_fmt}</b>\n\n"
        "Pilih tipe akses:",
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("🔗 Auto Generate Link Grup", callback_data="owner_vip_add_access_group")],
            [InlineKeyboardButton("🌐 Link Statis", callback_data="owner_vip_add_access_link")],
            [InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]
        ])
    )


async def _handle_vip_add_group(update, context, text):
    try:
        group_id = int(text.strip())
        if group_id > 0:
            group_id = -group_id
    except ValueError:
        await update.message.reply_text(
            "❌ Group ID harus berupa angka. Contoh: <code>-1003956111712</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]])
        )
        return

    await _finish_vip_add(update, context, group_id=group_id)


async def _handle_vip_add_link(update, context, text):
    link = text.strip()
    if link.lower().startswith("link="):
        link = link[5:].strip()
    if not link:
        await update.message.reply_text(
            "❌ Link tidak boleh kosong.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("❌ Batal", callback_data="owner_vip_menu")]])
        )
        return

    await _finish_vip_add(update, context, link=link)


async def _handle_vip_add(update, context, text):
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    from services.vip_service import add_package
    parts = text.split("|")
    if len(parts) < 3:
        await update.message.reply_text(
            "❌ Format kurang lengkap.\nContoh: <code>VIP PREMIUM|50000|-1003956111712</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_vip_menu")]])
        )
        return
    nama = parts[0].strip()
    try:
        harga = int(parts[1].strip())
    except ValueError:
        await update.message.reply_text("❌ Harga harus berupa angka.")
        return
    ketiga = parts[2].strip()
    group_id, link = None, None
    if ketiga.lower().startswith("link="):
        link = ketiga[5:].strip()
    else:
        try:
            group_id = int(ketiga)
            if group_id > 0:
                group_id = -group_id
        except ValueError:
            await update.message.reply_text("❌ Argumen ketiga harus Group ID atau link=URL.")
            return
    pkg = add_package(nama, harga, group_id=group_id, link=link)
    context.user_data["editing_vip_id"] = pkg["id"]
    context.user_data["waiting_vip_edit_banner"] = True
    context.user_data["vip_add_flow"] = True
    await update.message.reply_text(
        f"✅ Paket <b>{pkg['nama']}</b> berhasil ditambahkan!\n\n"
        "Sekarang kirim <b>foto banner</b> untuk paket ini.",
        parse_mode="HTML",
    )


async def _handle_promo_add(update, context, text):
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    from services.promo_service import create_promo, create_promo_percent
    args = text.strip().split()
    if len(args) != 3:
        await update.message.reply_text(
            "❌ Format salah.\nContoh: <code>HEMAT30 5 30%</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )
        return
    kode = args[0].upper()
    try:
        kuota = int(args[1])
    except ValueError:
        await update.message.reply_text("❌ Kuota harus berupa angka!")
        return
    nilai_str = args[2]
    if nilai_str.endswith("%"):
        try:
            persen = int(nilai_str.rstrip("%"))
        except ValueError:
            await update.message.reply_text("❌ Persen harus berupa angka, contoh: 30%")
            return
        create_promo_percent(kode, kuota, persen)
        await update.message.reply_text(
            f"✅ <b>Promo Berhasil Dibuat!</b>\n\n🎟️ Kode: <b>{kode}</b>\n👥 Kuota: {kuota} orang\n🏷️ Diskon: {persen}%",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )
    else:
        try:
            harga = int(nilai_str)
        except ValueError:
            await update.message.reply_text("❌ Harga harus berupa angka!")
            return
        create_promo(kode, kuota, harga)
        await update.message.reply_text(
            f"✅ <b>Promo Berhasil Dibuat!</b>\n\n🎟️ Kode: <b>{kode}</b>\n👥 Kuota: {kuota} orang\n💰 Harga Diskon: Rp {harga}",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )


async def _handle_hapus_link(update, context, text):
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    from services.link_service import find_link, revoke_link
    link_target = text.strip()
    link_data = find_link(link_target)
    if not link_data:
        await update.message.reply_text(
            "❌ Link tidak ditemukan di database aktif.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )
        return
    try:
        await context.bot.revoke_chat_invite_link(chat_id=link_data["chat_id"], invite_link=link_target)
        revoke_link(link_data)
        try:
            await context.bot.ban_chat_member(chat_id=link_data["chat_id"], user_id=link_data["user_id"])
            await context.bot.unban_chat_member(chat_id=link_data["chat_id"], user_id=link_data["user_id"])
            pesan_kick = f"\n👢 User ID {link_data['user_id']} berhasil dikeluarkan."
        except Exception:
            pesan_kick = "\n⚠️ User gagal dikeluarkan."
        await update.message.reply_text(
            f"✅ Link berhasil dihapus dari grup {link_data['group_name']}.{pesan_kick}",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )
    except Exception as e:
        await update.message.reply_text(f"⚠️ Gagal menghapus link: {e}")


async def _handle_edit_link(update, context, text):
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    from services.link_service import find_link
    parts = text.strip().split("|")
    if len(parts) < 2:
        await update.message.reply_text(
            "❌ Format salah.\nContoh: <code>https://t.me/+AbCdEfG|3</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )
        return
    link_target = parts[0].strip()
    try:
        new_limit = int(parts[1].strip())
    except ValueError:
        await update.message.reply_text("❌ Limit baru harus berupa angka!")
        return
    link_data = find_link(link_target)
    if not link_data:
        await update.message.reply_text(
            "❌ Link tidak ditemukan di database aktif.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )
        return
    try:
        await context.bot.edit_chat_invite_link(chat_id=link_data["chat_id"], invite_link=link_target, member_limit=new_limit)
        await update.message.reply_text(
            f"✅ Link berhasil diedit!\nBatas join baru: {new_limit} orang.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_group_product")]])
        )
    except Exception as e:
        await update.message.reply_text(f"⚠️ Gagal mengedit link: {e}")


async def _handle_add_admin(update, context, text):
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    from services.admin_service import add_admin
    parts = text.strip().split(None, 1)
    if len(parts) < 2:
        await update.message.reply_text(
            "❌ Format salah.\nContoh: <code>123456789 Budi</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_adminmenu")]])
        )
        return
    try:
        user_id = int(parts[0])
    except ValueError:
        await update.message.reply_text("❌ USER_ID harus berupa angka.")
        return
    nama_admin = parts[1].strip()
    if add_admin(user_id, nama_admin):
        await update.message.reply_text(
            f"✅ Admin {nama_admin} ({user_id}) berhasil ditambahkan.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_adminmenu")]])
        )
    else:
        await update.message.reply_text(
            "⚠️ User sudah menjadi admin.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_adminmenu")]])
        )


async def _handle_remove_admin(update, context, text):
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    from services.admin_service import remove_admin
    try:
        user_id = int(text.strip())
    except ValueError:
        await update.message.reply_text(
            "❌ USER_ID harus berupa angka.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_adminmenu")]])
        )
        return
    if remove_admin(user_id):
        await update.message.reply_text(
            f"🗑️ Admin {user_id} berhasil dihapus.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_adminmenu")]])
        )
    else:
        await update.message.reply_text(
            "⚠️ Admin tidak ditemukan.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="owner_adminmenu")]])
        )


async def _handle_vs_add(update, context, text):
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup
    from services.vs_service import add_vs_package
    if "|" not in text:
        await update.message.reply_text(
            "❌ Format salah.\nContoh: <code>VCS 5 Menit|50000</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="vs_manage")]])
        )
        return
    nama, harga = text.split("|", 1)
    target_admin = context.user_data.get("vs_target_admin", update.effective_user.id)
    add_vs_package(target_admin, nama.strip(), harga.strip())
    await update.message.reply_text(
        f"✅ Paket '{nama.strip()}' berhasil ditambahkan.",
        reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Kembali", callback_data="vs_manage")]])
    )


async def _handle_hapus_manual_input(update, context, text, pkg_id):
    from services.link_service import get_links_by_chat_id, get_links_by_group_name
    from services.vip_service import get_package_by_id

    try:
        target_uid = int(text.strip())
    except ValueError:
        await update.message.reply_text(
            "❌ User ID harus berupa angka.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_userid_{pkg_id}")]
            ])
        )
        return

    pkg = get_package_by_id(pkg_id)
    if not pkg:
        await update.message.reply_text("❌ Paket tidak ditemukan.")
        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"])

    found = next((x for x in links if x["user_id"] == target_uid), None)
    if not found:
        await update.message.reply_text(
            f"❌ User ID <code>{target_uid}</code> tidak ditemukan di paket <b>{pkg['nama']}</b>.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_userid_{pkg_id}")]
            ])
        )
        return

    un = found.get("username") or "-"
    uname = f"@{un}" if un and un != "-" else "-"
    await update.message.reply_text(
        f"⚠️ <b>Konfirmasi Penghapusan</b>\n\n"
        f"👤 User ID : <code>{found['user_id']}</code>\n"
        f"📛 Username: {uname}\n"
        f"📦 Paket   : {found['group_name']}\n\n"
        f"Yakin ingin menghapus data ini?",
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup([
            [
                InlineKeyboardButton("✅ Hapus", callback_data=f"listlink_konfirm_hapus_{found['id']}_{pkg_id}"),
                InlineKeyboardButton("❌ Batal", callback_data=f"listlink_batal_hapus_{pkg_id}"),
            ]
        ])
    )


async def _handle_member_search(update, context, text, pkg_id):
    from services.link_service import get_links_by_chat_id, get_links_by_group_name
    from services.vip_service import get_package_by_id

    pkg = get_package_by_id(pkg_id)
    if not pkg:
        await update.message.reply_text("❌ Paket tidak ditemukan.")
        return

    keyword = text.strip().lstrip("@").lower()
    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"])

    found = None
    for item in links:
        username = (item.get("username") or "").lstrip("@").lower()
        if str(item.get("user_id")) == keyword or (keyword and username == keyword):
            found = item
            break

    if not found:
        await update.message.reply_text(
            f"❌ User <code>{html.escape(text.strip())}</code> tidak ditemukan di paket <b>{html.escape(pkg['nama'])}</b>.",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("🔍 Cari Lagi", callback_data=f"listlink_search_{pkg_id}")],
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_paket_{pkg_id}")]
            ])
        )
        return

    username = found.get("username") or "-"
    username_text = f"@{username}" if username and username != "-" else "-"
    await update.message.reply_text(
        "👤 <b>Member ditemukan</b>\n\n"
        f"Username: {html.escape(username_text)}\n"
        f"ID: <code>{found['user_id']}</code>\n"
        f"Paket: <b>{html.escape(found.get('group_name') or pkg['nama'])}</b>\n"
        f"Status Link: <b>{html.escape(found.get('status') or '-')}</b>\n"
        f"Join Date: {html.escape(str(found.get('join_date') or '-'))}",
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("👤 Buka Detail", callback_data=f"listlink_member_{found['id']}_{pkg_id}")],
            [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_paket_{pkg_id}")]
        ])
    )


async def _handle_manual_add(update, context, text, pkg_id):
    from services.vip_service import get_package_by_id
    from services.link_service import add_link

    parts = [p.strip() for p in text.strip().split("|")]
    if len(parts) < 3:
        await update.message.reply_text(
            "❌ Format salah. Gunakan:\n<code>user_id|username|link</code>",
            parse_mode="HTML",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_paket_{pkg_id}")]
            ])
        )
        return

    try:
        uid = int(parts[0])
    except ValueError:
        await update.message.reply_text(
            "❌ user_id harus berupa angka.",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_paket_{pkg_id}")]
            ])
        )
        return

    uname = parts[1].lstrip("@").strip() or "-"
    if uname == "-":
        uname = "-"
    link  = parts[2]

    pkg = get_package_by_id(pkg_id)
    if not pkg:
        await update.message.reply_text("❌ Paket tidak ditemukan.")
        return

    add_link(
        link=link,
        chat_id=pkg["group_id"] or 0,
        group_name=pkg["nama"],
        user_id=uid,
        username=uname
    )

    uname_display = f"@{uname}" if uname and uname != "-" else "-"
    await update.message.reply_text(
        f"✅ <b>Data berhasil ditambahkan!</b>\n\n"
        f"👤 User ID : <code>{uid}</code>\n"
        f"📛 Username: {uname_display}\n"
        f"📦 Paket   : {pkg['nama']}\n"
        f"🔗 Link    : {link}",
        parse_mode="HTML",
        disable_web_page_preview=True,
        reply_markup=InlineKeyboardMarkup([
            [InlineKeyboardButton("⬅️ Kembali", callback_data=f"listlink_paket_{pkg_id}")]
        ])
    )


async def _send_take_all_payment_selection(update, context, caption_order):
    context.user_data["payment_caption"] = caption_order

    info = get_payment_info()
    pay_keyboard = []
    if info.get("qris_file_id") or os.path.exists(QRIS_PATH):
        pay_keyboard.append([InlineKeyboardButton(
            "QRIS",
            callback_data="pay_method_qris",
            **premium_button_icon_kwargs(QRIS_BUTTON_ICON_ID)
        )])
    if info.get("dana_number"):
        pay_keyboard.append([InlineKeyboardButton(
            "Dana",
            callback_data="pay_method_dana",
            **premium_button_icon_kwargs(DANA_BUTTON_ICON_ID)
        )])
    pay_keyboard.append([InlineKeyboardButton("❌ Batalkan", callback_data="batal_transaksi")])

    sent = await update.message.reply_text(
        caption_order + "\n\n💳 <b>Pilih metode pembayaran:</b>",
        parse_mode="HTML",
        reply_markup=InlineKeyboardMarkup(pay_keyboard)
    )
    context.user_data["qris_msg_id"] = sent.message_id


async def _handle_take_all_promo(update, context, text):
    kode_input = text.strip().upper()
    promo = get_promo(kode_input)

    if not promo:
        await update.message.reply_text(
            "❌ Kode promo tidak valid atau tidak ditemukan."
        )
        return

    if promo.get("type") != "percent":
        await update.message.reply_text(
            "❌ Kode promo ini tidak berlaku untuk Take All."
        )
        return

    if not use_promo(kode_input):
        await update.message.reply_text("❌ Kuota kode promo sudah habis.")
        return

    persen = int(promo["persen"])
    # Harga setelah diskon 20% Take All sudah tersimpan di vip_harga_bayar
    harga_setelah_takeall = context.user_data.get(
        "vip_take_all_subtotal",
        context.user_data.get("vip_harga_bayar", 0)
    )
    diskon = int(harga_setelah_takeall * persen / 100)
    bayar = harga_setelah_takeall - diskon

    context.user_data.pop("waiting_take_all_promo", None)
    context.user_data["promo_applied"] = kode_input
    context.user_data["promo_discount_pct"] = persen
    context.user_data["promo_discount_amount"] = diskon
    context.user_data["vip_harga_bayar"] = bayar
    context.user_data["take_all_promo_done"] = True
    context.user_data["status_transaksi"] = "pending"
    context.user_data["bukti_dikirim"] = False
    context.user_data.setdefault("transaction_back_view", "vip_take_menu")

    caption_order = (
        f"✅ Promo <b>{html.escape(kode_input)}</b> berhasil dipakai\n\n"
        f"Subtotal: {_format_rupiah(harga_setelah_takeall)}\n"
        f"Diskon Promo {persen}%: -{_format_rupiah(diskon)}\n"
        f"💰 <b>Total Bayar: {_format_rupiah(bayar)}</b>"
    )
    await _send_take_all_payment_selection(update, context, caption_order)
