# -*- coding: utf-8 -*-
import logging
import asyncio
import re
import json
import tempfile
import os as _os
import aiohttp
from threading import Thread
from flask import Flask
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    ApplicationBuilder, CommandHandler, CallbackQueryHandler,
    MessageHandler, ContextTypes, filters
)
from pymongo import MongoClient
import nest_asyncio
from datetime import datetime, timedelta

nest_asyncio.apply()

# --- DNS FIX ---
try:
    import dns.resolver
    dns.resolver.default_resolver = dns.resolver.Resolver(configure=False)
    dns.resolver.default_resolver.nameservers = ['8.8.8.8', '1.1.1.1']
except Exception:
    pass

# --- FLASK SERVER ---
flask_app = Flask('')

@flask_app.route('/')
def home():
    return "Bot is Alive!"

def run_flask():
    port = int(_os.environ.get("PORT", 8080))
    flask_app.run(host='0.0.0.0', port=port, use_reloader=False)

def keep_alive():
    t = Thread(target=run_flask, daemon=True)
    t.start()

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)

# ========================================================
# =================== BOT CONFIGS ========================
# ========================================================

BOTS = [
    {
        "BOT_TOKEN":     "8685890491:AAFM1aqPe1ZbenJ-1lwcChMclArP643Doko",
        "ADMIN_ID":      8777992150,
        "ADMIN_ID_2":    8777992150,            # ← Doosre admin ka Telegram ID yahan daalo (0 = disabled)
        "FORCE_CHANNEL": "@technostellar_official",
        "DEV_CHAT":      "https://t.me/kingxsellerg1",
        "DB_NAME":       "Avi_Osint_DB_2",
    },
]

REPLACE_TO = "@kingxsellerg1"
AVI_HEADER = "𝗔𝗩𝗜 𝗢𝗦𝗜𝗡𝗧 🔍"

# ── QR Image URL (Google Drive) ──
QR_IMAGE_URL = "https://drive.google.com/uc?export=download&id=1w_W9aWCvTKvhkh7fDp0bjnfgTTKX4_Xt"

# ========= MONGODB =========
mongo_client = MongoClient(
    "MONGO_URL",
    connect=False
)

# ========= USER BOTS =========
# Structure: {token: {"app": app, "owner_id": int, "username": str, "parent_token": str}}
user_bots = {}

# Stopped bots registry (RAM cache — also persisted to MongoDB for restart survival)
# Structure: {token: {"owner_id": int, "username": str, "parent_token": str, "admin_id": int}}
stopped_bots = {}

# ── Persistent bot registry helpers ──
_registry_db  = mongo_client["AVI_BOT_REGISTRY"]
_stopped_col  = _registry_db["stopped_bots"]
_running_col  = _registry_db["running_bots"]   # ← NEW: survive restart

# ── stopped_bots helpers ──
def persist_stopped_bot(token: str, info: dict):
    _stopped_col.update_one({"token": token}, {"$set": {**info, "token": token}}, upsert=True)

def remove_persisted_stopped_bot(token: str):
    _stopped_col.delete_one({"token": token})

# ── running_bots helpers ──
def persist_running_bot(token: str, info: dict):
    """Save running bot metadata to MongoDB so restart can re-launch it."""
    _running_col.update_one(
        {"token": token},
        {"$set": {
            "token":          token,
            "owner_id":       info.get("owner_id"),
            "username":       info.get("username", "unknown"),
            "parent_token":   info.get("parent_token", ""),
            "admin_id":       info.get("admin_id"),
            "parent_dev_chat":info.get("parent_dev_chat", ""),
        }},
        upsert=True
    )

def remove_persisted_running_bot(token: str):
    _running_col.delete_one({"token": token})

async def load_and_relaunch_mirror_bots():
    """On main bot startup: re-launch all previously running mirror bots from DB."""
    docs = list(_running_col.find({}))
    for doc in docs:
        tok = doc.get("token")
        if not tok or tok in user_bots:
            continue
        owner_id    = doc.get("owner_id", 0)
        admin_id    = doc.get("admin_id", owner_id)
        parent_tok  = doc.get("parent_token", "")
        p_dev_chat  = doc.get("parent_dev_chat", "")
        logging.info(f"♻️ Re-launching mirror bot from DB: {tok[:20]}...")
        success, msg = await launch_user_bot(
            tok, owner_id,
            parent_dev_chat=p_dev_chat,
            parent_token=parent_tok,
            custom_admin_id=admin_id
        )
        if not success:
            logging.warning(f"Re-launch failed for {tok[:20]}: {msg}")
            # Agar re-launch fail ho to stopped_bots mein daal do (not lost)
            stopped_bots[tok] = {
                "owner_id":     owner_id,
                "username":     doc.get("username", "unknown"),
                "parent_token": parent_tok,
                "admin_id":     admin_id,
                "parent_dev_chat": p_dev_chat,
            }
            persist_stopped_bot(tok, stopped_bots[tok])

def load_stopped_bots_from_db():
    for doc in _stopped_col.find({}):
        tok = doc.get("token")
        if tok and tok not in user_bots:
            stopped_bots[tok] = {
                "owner_id":      doc.get("owner_id"),
                "username":      doc.get("username", "unknown"),
                "parent_token":  doc.get("parent_token", ""),
                "admin_id":      doc.get("admin_id"),
                "parent_dev_chat": doc.get("parent_dev_chat", ""),
            }

# ========================================================
# ========== DIRECT API URLS (No Middleman) ==============
# ========================================================

API_URLS = {
    "NUMBER":      "https://api.example.com/number?query={term}",
    "AADHAR":      "https://api.example.com/aadhar?query={term}",
    "TG_USERNAME": "https://api.example.com/telegram?query={term}",
    "FAMILY":      "https://api.example.com/family?query={term}",
    "RATIONS_INFO": "https://api.example.com/rations?query={term}",
    "VEHICLE":     "https://api.example.com/vehicle?query={term}",
    "CAR":         "https://api.example.com/car?query={term}",
    "VEH_TO_NUM":  "https://api.example.com/veh-to-num?query={term}",
    "PAN":         "https://api.example.com/pan?query={term}",
    "INSTAGRAM":   "https://api.example.com/instagram?query={term}",
    "FREE_FIRE":   "https://api.example.com/freefire?uid={term}",
    "UPI_INFO":    "https://api.example.com/upi?query={term}",
    # EMAIL_LOOKUP has no API yet — handled gracefully
}

CREDIT_COSTS = {
    "TG_USERNAME":  10.0,
    "VEH_TO_NUM":   10.0,
    "NUMBER":        2.0,
    "AADHAR":        2.0,
    "VEHICLE":       2.0,
    "CAR":           2.0,
    "FAMILY":        2.0,
    "RATIONS_INFO":  2.0,
    "PAN":           2.0,
    "EMAIL_LOOKUP":  2.0,
    "INSTAGRAM":     2.0,
    "FREE_FIRE":     2.0,
    "UPI_INFO":      2.0,
}

# Fields to strip from API response (filter unwanted meta keys)
FILTER_KEYS = {
    "warning", "api", "owner", "success", "message", "msg",
    "cached", "proxyused", "attempt", "injected_ip",
}

# ========================================================
# =================== FIELD MAP ==========================
# ========================================================

FIELD_MAP = {
    "name":        ("👤", "Name"),
    "fullname":    ("👤", "Name"),
    "full_name":   ("👤", "Name"),
    "id":          ("🆔", "UID"),
    "uid":         ("🆔", "UID"),
    "id_number":   ("🆔", "UID"),
    "aadhar":      ("🆔", "UID"),
    "aadhar_no":   ("🆔", "UID"),
    "aadhaar":     ("🆔", "UID"),
    "aadhaar_no":  ("🆔", "UID"),
    "address":     ("🏠", "Address"),
    "addr":        ("🏠", "Address"),
    "doc":         ("📄", "Doc"),
    "document":    ("📄", "Doc"),
    "dob":         ("📅", "DOB"),
    "father":      ("👨", "Father Name"),
    "father_name": ("👨", "Father Name"),
    "fathername":  ("👨", "Father Name"),
    "fname":       ("👨", "Father Name"),

    "alt":         ("📞", "Alt Number"),
    "alt_number":  ("📞", "Alt Number"),
    "alt_mobile":  ("📞", "Alt Number"),
    "altnumber":   ("📞", "Alt Number"),
    "alternate":   ("📞", "Alt Number"),
    "username":    ("👤", "Username"),
    "user_id":     ("🆔", "UID"),
    "userid":      ("🆔", "UID"),
    "tg_id":       ("🆔", "UID"),
    "telegram_id": ("🆔", "UID"),
    "first_name":  ("👤", "First Name"),
    "last_name":   ("👤", "Last Name"),
    "bio":         ("📝", "Bio"),
    "circle":      ("??", "Circle"),
    "email":       ("📧", "Email"),
    "mobile":      ("📱", "Mobile"),
    "phone":       ("📱", "Phone"),
    "number":      ("📱", "Number"),
    "pan":         ("💳", "PAN"),
    "vehicle":     ("🚗", "Vehicle"),
    "state":       ("🗺️", "State"),
    "city":        ("🏙️", "City"),
    "pincode":     ("📮", "Pincode"),
    "gender":      ("⚧️", "Gender"),
    "operator":    ("📡", "Operator"),
    "telecom":     ("📡", "Telecom"),
    "registration":("📋", "Registration"),
    "owner_name":  ("👤", "Owner Name"),
    "chassis":     ("🔩", "Chassis"),
    "engine":      ("⚙️", "Engine"),
    "model":       ("🚘", "Model"),
    "fuel":        ("⛽", "Fuel"),
}

MASK_KEYS = {
    "name", "address", "father_name", "father", "alt_number",
    "alt_mobile", "alt", "alternate", "mobile", "phone",
    "number", "id", "uid", "id_number", "aadhar", "email",
    "pan", "vehicle", "dob", "owner_name"
}

# ========================================================
# =================== HELPERS ============================
# ========================================================

def get_db(db_name):
    db = mongo_client[db_name]
    return {
        "users":        db['users'],
        "checkin":      db['checkins'],
        "protect":      db['protected_inputs'],
        "redeem":       db['redeem_codes'],
        "pending_bots": db['pending_bots'],
    }


def get_effective_credits(cols, user_doc) -> float:
    """
    Return user's currently valid credits.
    - If user has active unlimited plan: return 999999 (sentinel).
    - safe_credits (refer + checkin earned) NEVER expire — return them always.
    - Admin-added credits (main 'credits' field):
        * Agar expiry define nahi hai → 30 April 2026 IST (17:30 UTC) tak expire
        * Agar expiry define hai aur 30 April se pehle/same → 30 April ko expire
        * Agar expiry 30 April ke baad → apni date pe expire
        * Agar already expired → 0
    - Total = valid admin credits + safe_credits (unexpired)
    """
    if not user_doc:
        return 0.0

    now = datetime.utcnow()

    # ── Global free session check (admin /free command) ──
    try:
        free_doc = cols["users"].find_one({"user_id": "FREE_SESSION"})
        if free_doc and free_doc.get("active") and free_doc.get("free_until"):
            if free_doc["free_until"] > now:
                return 999999.0
            else:
                cols["users"].update_one(
                    {"user_id": "FREE_SESSION"},
                    {"$set": {"active": False}}
                )
    except Exception:
        pass

    # ── Unlimited plan check ──
    if user_doc.get("unlimited_plan"):
        unlim_exp = user_doc.get("unlimited_expires_at")
        if unlim_exp and unlim_exp > now:
            return 999999.0
        else:
            cols["users"].update_one(
                {"user_id": user_doc["user_id"]},
                {"$set": {"unlimited_plan": False}}
            )

    # ── 30 April 2026, 11:59 PM IST = 17:29 UTC ──
    MASS_EXPIRY = datetime(2027, 12, 31, 23, 59, 59)

    # ── Safe credits (refer + checkin) — NEVER expire ──
    safe_credits = float(user_doc.get("safe_credits", 0.0))

    # ── Admin-added credits ──
    admin_credits = float(user_doc.get("credits", 0.0))
    expires       = user_doc.get("credits_expires_at")

    if admin_credits > 0:
        # Effective expiry decide karo
        if expires is None:
            eff_expiry = MASS_EXPIRY
            # Lazily set karo DB mein
            cols["users"].update_one(
                {"user_id": user_doc["user_id"]},
                {"$set": {"credits_expires_at": MASS_EXPIRY}}
            )
        else:
            # BUG FIX: Actual expiry use karo — DB mein jo date hai wahi valid hai
            eff_expiry = expires

        if now >= eff_expiry:
            # Admin credits expire ho gaye — zero out
            cols["users"].update_one(
                {"user_id": user_doc["user_id"]},
                {"$set": {"credits": 0.0}}
            )
            admin_credits = 0.0
    else:
        admin_credits = 0.0

    return admin_credits + safe_credits


def init_user(cols, user_id, ref_by=None, context=None, username=None):
    u_id = str(user_id)
    user = cols["users"].find_one({"user_id": u_id})
    if not user:
        user_data = {
            "user_id":           u_id,
            "username":          username or "",
            "credits":            2.0,
"credits_expires_at": datetime.utcnow() + timedelta(days=365),
            "refer_count":       0,
            "referred_by":       ref_by,
            "joined_at":         datetime.utcnow()
        }
        cols["users"].insert_one(user_data)
        if ref_by and str(ref_by) != u_id:
            cols["users"].update_one(
                {"user_id": str(ref_by)},
                {"$inc": {"safe_credits": 1.0, "refer_count": 1}}
            )
            if context:
                try:
                    asyncio.create_task(
                        context.bot.send_message(
                            chat_id=int(ref_by),
                            text="🎁 *+1 Refer Credit Mila!*\n\nKisi ne aapka referral link use kiya.\n💳 *1 Free Credit add ho gaya!* (No Expiry)",
                            parse_mode="Markdown",
                            reply_markup=InlineKeyboardMarkup([
                                [InlineKeyboardButton("💰 My Credits", callback_data="MAIN_MENU")]
                            ])
                        )
                    )
                except Exception:
                    pass
        return user_data
    # Update username if provided
    if username:
        cols["users"].update_one({"user_id": u_id}, {"$set": {"username": username}})
    return user


def clean_phone_number(text: str) -> str:
    """Extract exactly 10-digit Indian mobile number."""
    digits = re.sub(r'\D', '', text)
    if len(digits) == 12 and digits.startswith('91'):
        digits = digits[2:]
    elif len(digits) == 11 and digits.startswith('0'):
        digits = digits[1:]
    return digits


def clean_output(text: str) -> str:
    text = re.sub(r'@?@avi502930', '@avi5029', text, flags=re.IGNORECASE)
    text = re.sub(avi502930', 'AVI OSINT', text, flags=re.IGNORECASE)
    text = re.sub(r'avi502930', 'AVI OSINT', text)
    text = re.sub(r'@avi502930', REPLACE_TO, text, flags=re.IGNORECASE)
    text = re.sub(r'encorex', 'AVI', text, flags=re.IGNORECASE)
    text = re.sub(r'api\s+by\s+PYOSINTX', REPLACE_TO, text, flags=re.IGNORECASE)
    text = re.sub(r'PYOSINTX', REPLACE_TO, text, flags=re.IGNORECASE)
    text = re.sub(r'API BY Avi \(simbogue\)', REPLACE_TO, text, flags=re.IGNORECASE)
    text = re.sub(r'ZX OSINT', REPLACE_TO, text, flags=re.IGNORECASE)
    text = re.sub(r'dimchan', '@Avi5029', text, flags=re.IGNORECASE)
    text = re.sub(r'TryByte\s*\|\|\s*Ankucode', '@aviosint', text, flags=re.IGNORECASE)
    text = re.sub(r'aviraj', '@aviosint', text, flags=re.IGNORECASE)
    return text


def filter_api_response(data: dict) -> dict:
    """Remove meta/warning fields from API response before showing to user."""
    return {k: v for k, v in data.items() if k.lower() not in FILTER_KEYS}


def build_structured_form(data: dict, do_mask: bool = False) -> str:
    """Build clean bold-label format — no monospace padding."""
    lines = []
    for k, v in data.items():
        if v is None or str(v).strip() in ("", "null", "None", "N/A"):
            continue
        key_lower = k.lower().replace(" ", "_")
        val_str   = str(v).strip()
        if do_mask and key_lower in MASK_KEYS and len(val_str) > 4:
            val_str = val_str[:2] + "*" * (len(val_str) - 4) + val_str[-2:]
        if key_lower in FIELD_MAP:
            icon, label = FIELD_MAP[key_lower]
            lines.append(f"{icon} *{label}:* `{val_str}`")
        else:
            label = k.replace("_", " ").title()
            lines.append(f"📌 *{label}:* `{val_str}`")
    return "\n".join(lines)


# Ordered field display for NUMBER and AADHAR lookups
NUMBER_FIELD_ORDER = [
    "name", "fullname", "full_name",
    "father", "father_name", "fathername", "fname",
    "address", "addr",
    "circle",
    "mobile", "phone", "number",
    "alt", "alt_number", "alt_mobile", "altnumber", "alternate",
    "id", "uid", "id_number", "aadhar", "aadhar_no", "aadhaar", "aadhaar_no",
    "dob", "gender", "email", "state", "city", "pincode", "operator", "telecom",
]

AADHAR_FIELD_ORDER = [
    "name", "fullname", "full_name",
    "father", "father_name", "fathername", "fname",
    "dob", "gender",
    "address", "addr",
    "mobile", "phone", "number",
    "alt", "alt_number", "alt_mobile", "altnumber", "alternate",
    "id", "uid", "id_number", "aadhar", "aadhar_no", "aadhaar", "aadhaar_no",
    "email", "state", "city", "pincode",
]

TG_FIELD_ORDER = [
    "name", "fullname", "full_name", "first_name", "last_name",
    "username",
    "mobile", "phone", "number",
    "id", "uid", "id_number", "user_id", "userid", "tg_id", "telegram_id",
    "bio", "email", "dob", "address", "circle",
]


def build_ordered_form(data: dict, field_order: list, do_mask: bool = False) -> str:
    """Build structured output in a fixed field order. Clean bold-label format."""
    lines = []
    used_keys = set()

    def _add_line(k, v):
        if v is None or str(v).strip() in ("", "null", "None", "N/A"):
            return
        key_lower = k.lower().replace(" ", "_")
        val_str = str(v).strip()
        if do_mask and key_lower in MASK_KEYS and len(val_str) > 4:
            val_str = val_str[:2] + "*" * (len(val_str) - 4) + val_str[-2:]
        if key_lower in FIELD_MAP:
            icon, label = FIELD_MAP[key_lower]
            lines.append(f"{icon} *{label}:* `{val_str}`")
        else:
            label = k.replace("_", " ").title()
            lines.append(f"\U0001f4cc *{label}:* `{val_str}`")

    data_lower = {k.lower(): (k, v) for k, v in data.items()}
    for ordered_key in field_order:
        if ordered_key in data_lower:
            orig_k, v = data_lower[ordered_key]
            _add_line(ordered_key, v)
            used_keys.add(ordered_key)

    for k, v in data.items():
        if k.lower() not in used_keys:
            _add_line(k, v)

    return "\n".join(lines)


def build_multi_record_message(unique: list, do_mask: bool = False, max_records: int = 3) -> str:
    """Build numbered RECORD blocks — clean bold-label format."""
    blocks = []
    sep = "─" * 28
    for i, entry in enumerate(unique[:max_records], 1):
        normalised = {k.lower(): v for k, v in entry.items()}
        lines = [f"📋 *RECORD #{i}*", f"`{sep}`"]
        for k, v in normalised.items():
            if v is None or str(v).strip() in ("", "null", "None", "N/A"):
                continue
            key_lower = k.lower().replace(" ", "_")
            val_str   = str(v).strip()
            if do_mask and key_lower in MASK_KEYS and len(val_str) > 4:
                val_str = val_str[:2] + "*" * (len(val_str) - 4) + val_str[-2:]
            if key_lower in FIELD_MAP:
                icon, label = FIELD_MAP[key_lower]
                lines.append(f"{icon} *{label}:* `{val_str}`")
            else:
                label = k.replace("_", " ").title()
                lines.append(f"📌 *{label}:* `{val_str}`")
        blocks.append("\n".join(lines))
    return "\n\n".join(blocks)


def mask_text(text: str) -> str:
    if not text:
        return ""
    lines, masked_lines = text.split('\n'), []
    targets = ["NAME", "ADDRESS", "ALT", "FATHER", "DOB", "VOTER",
               "PHONE", "ID", "AADHAR", "VEHICLE", "CAR", "PAN", "MOBILE", "NUMBER"]
    for line in lines:
        if ":" in line:
            parts = line.split(":", 1)
            key, val = parts[0].strip(), parts[1].strip()
            if any(t in key.upper() for t in targets) and len(val) > 4:
                masked_lines.append(f"{key}: {val[:2]}{'*'*(len(val)-4)}{val[-2:]}")
            else:
                masked_lines.append(f"{key}: {'*'*len(val)}")
        else:
            masked_lines.append(line)
    return "\n".join(masked_lines)


def is_protected(cols, input_value: str) -> bool:
    val = input_value.strip().lower().lstrip('@')
    return cols["protect"].find_one({"value": val}) is not None


def is_bot_stopped(cols) -> bool:
    """Check if admin has issued /stop command."""
    doc = cols["users"].find_one({"user_id": "BOT_SYSTEM_STATUS"})
    return bool(doc and doc.get("bot_stopped", False))


async def call_api(url: str):
    """Make GET request to external API. Returns dict if JSON, else raw text string."""
    async with aiohttp.ClientSession() as session:
        async with session.get(url, timeout=aiohttp.ClientTimeout(total=20)) as resp:
            raw_text = await resp.text()
            try:
                return await resp.json(content_type=None)
            except Exception:
                return raw_text.strip()


async def auto_delete_message(msg, delay: int = 10):
    """Delete message after `delay` seconds."""
    await asyncio.sleep(delay)
    try:
        await msg.delete()
    except Exception:
        pass


# ========================================================
# ============= DIRECT API RESULT SENDER =================
# ========================================================

async def send_api_result(update: Update, context, cols, user_doc, mode: str, term: str, wait_msg):
    """
    Call API directly, validate success, format result, send with
    auto-delete warning. Deducts credit ONLY on success.
    """
    user_id     = str(update.effective_user.id)
    credit_cost = CREDIT_COSTS.get(mode, 2.0)
    tg_mode     = (mode == "TG_USERNAME")

    # Refresh user_doc for latest credits
    user_doc = cols["users"].find_one({"user_id": user_id}) or user_doc

    # ── Credit expiry check ──
    u_credits = get_effective_credits(cols, user_doc)

    # ── Insufficient credits ──
    if u_credits < credit_cost:
        await wait_msg.edit_text(
            "╔═══════════════════════════╗\n"
            "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
            "╚═══════════════════════════╝\n\n"
            "💔 *Aapke account mein credit khatam ho gaya!*\n\n"
            "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
            f"💳 *Your Balance:*  `{u_credits} Credits`\n"
            f"🔍 *Required:*  `{int(credit_cost)} Credits`\n"
            "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
            "📦 *Credit kharido — Full Access Pao!*\n"
            "⬇️ Niche *💰 BUY CREDIT* button dabao",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
            ]),
            parse_mode="Markdown"
        )
        return

    # ── No API configured for this mode ──
    if mode not in API_URLS:
        await wait_msg.edit_text(
            "🔜 *API not yet configured for this feature.*\n\nJaldi available hoga!",
            parse_mode="Markdown",
            reply_markup=back_to_main_keyboard()
        )
        return

    # ── Call API ──
    url = API_URLS[mode].format(term=term)
    try:
        raw_data = await call_api(url)
    except asyncio.TimeoutError:
        await wait_msg.edit_text(
            "❌ *API Timeout!*\n\nServer response nahi aa raha. Dobara try karo.\n_(No credit deducted)_",
            parse_mode="Markdown",
            reply_markup=back_to_main_keyboard()
        )
        return
    except Exception as e:
        await wait_msg.edit_text(
            f"❌ *API Error:* `{str(e)}`\n\n_(No credit deducted)_",
            parse_mode="Markdown",
            reply_markup=back_to_main_keyboard()
        )
        return

    # ── NUMBER / AADHAR: exploitsindia API — plain text response ──
    # Response format: plain formatted text (no JSON)
    # 1st result → screen pe dikhao, full result → .txt file mein
    if mode in ("NUMBER", "AADHAR") and isinstance(raw_data, str):
        cleaned = clean_output(raw_data)
        if not cleaned.strip():
            await wait_msg.edit_text(
                "⚠️ *DATA NOT AVAILABLE* ⚠️\n━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
                f"🔍 *Is {'number' if mode == 'NUMBER' else 'Aadhaar'} ke liye koi data nahi mila.*\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                "_(Koi credit deduct nahi kiya gaya)_",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
            return

        # ── Split multiple results (exploitsindia separates results by blank lines or dashes) ──
        # Ek result ek block hota hai — dashes ya double newline se alag
        def split_results(text: str):
            """Split plain-text into individual result blocks."""
            # Try splitting by lines of dashes/equals (separator lines)
            sep_pattern = re.compile(r'\n[-=]{5,}\n')
            parts = sep_pattern.split(text)
            # Filter empty blocks
            blocks = [p.strip() for p in parts if p.strip()]
            if len(blocks) > 1:
                return blocks
            # Fallback: split by double newline
            parts = re.split(r'\n\s*\n\s*\n', text)
            blocks = [p.strip() for p in parts if p.strip()]
            return blocks if len(blocks) > 1 else [text.strip()]

        all_results = split_results(cleaned)
        first_result = all_results[0]
        total_count  = len(all_results)

        # ── Screen pe 1st result dikhao ──
        header_text = f"*{AVI_HEADER}*\n\n"
        screen_note = f"\n\n📊 *Total Results: {total_count}* _(Full data file mein hai)_" if total_count > 1 else ""
        footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"

        screen_msg = header_text + f"```\n{first_result}\n```" + screen_note + footer_text
        if len(screen_msg) > 4000:
            screen_msg = header_text + f"```\n{first_result[:3700]}\n...(truncated)\n```" + screen_note + footer_text

        try:
            sent = await wait_msg.edit_text(screen_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
        except Exception:
            try:
                sent = await wait_msg.edit_text(screen_msg, reply_markup=back_to_main_keyboard())
            except Exception:
                sent = await context.bot.send_message(chat_id=int(user_id), text=screen_msg, reply_markup=back_to_main_keyboard())
        asyncio.create_task(auto_delete_message(sent, delay=20))

        # ── Full result → .txt file (with replacement already done by clean_output) ──
        file_content_lines = [
            "=" * 48,
            f"  {AVI_HEADER}  —  FULL RESULT EXPORT",
            "=" * 48,
            f"  Total Records: {total_count}",
            "=" * 48,
            "",
        ]
        for idx, block in enumerate(all_results, 1):
            file_content_lines.append(f"RECORD #{idx}")
            file_content_lines.append("-" * 40)
            file_content_lines.append(block)
            file_content_lines.append("")
        file_content_lines.append("=" * 48)
        file_content_lines.append(f"  Powered by {AVI_HEADER}  |  @avi5029")
        file_content_lines.append("=" * 48)
        file_text = "\n".join(file_content_lines)

        try:
            with tempfile.NamedTemporaryFile(
                mode='w', suffix='.txt', delete=False,
                prefix='result_', encoding='utf-8'
            ) as f:
                f.write(file_text)
                tmp_path = f.name
            file_msg = await context.bot.send_document(
                chat_id=int(user_id),
                document=open(tmp_path, 'rb'),
                filename=f"{'number' if mode == 'NUMBER' else 'aadhar'}_result.txt",
                caption=f"📄 *Full {'Number' if mode == 'NUMBER' else 'Aadhaar'} Result*\n"
                        f"📊 Records: `{total_count}`\n"
                        "⚠️ This file will auto-delete in 20 seconds.",
                parse_mode="Markdown"
            )
            asyncio.create_task(auto_delete_message(file_msg, delay=20))
        except Exception as fe:
            logging.warning(f"TXT file send error: {fe}")
        finally:
            try:
                _os.unlink(tmp_path)
            except Exception:
                pass

        # ── Credit deduct ──
        user_doc_fresh = cols["users"].find_one({"user_id": user_id})
        if get_effective_credits(cols, user_doc_fresh) < 999999.0:
            cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
        return

    # ── TG_USERNAME / VEHICLE / CAR: agar response plain text hai (JSON nahi) to directly bhej do ──
    if mode in ("TG_USERNAME", "VEHICLE", "CAR") and isinstance(raw_data, str):
        cleaned = clean_output(raw_data)
        cleaned = re.sub(r'@?Cyb3rS0ldier', '@avi5029', cleaned, flags=re.IGNORECASE)
        if not cleaned:
            await wait_msg.edit_text(
                "⚠️ *DATA NOT AVAILABLE*\n\n"
                "🔍 *Is username ke liye koi data nahi mila.*\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                "_(Koi credit deduct nahi kiya gaya)_",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
            return
        header_text = f"*{AVI_HEADER}*\n\n"
        footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
        full_msg = header_text + cleaned + footer_text
        if len(full_msg) > 4000:
            full_msg = header_text + cleaned[:3900] + "\n\n_(truncated)_" + footer_text
        try:
            sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
        except Exception:
            try:
                sent = await wait_msg.edit_text(full_msg, reply_markup=back_to_main_keyboard())
            except Exception:
                sent = await context.bot.send_message(chat_id=int(user_id), text=full_msg, reply_markup=back_to_main_keyboard())
        asyncio.create_task(auto_delete_message(sent, delay=20))
        user_doc_fresh = cols["users"].find_one({"user_id": user_id})
        if get_effective_credits(cols, user_doc_fresh) < 999999.0:
            cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
        return

    # ── Handle list response (e.g. new AADHAR API returns array directly) ──
    if isinstance(raw_data, list):
        if raw_data:
            raw_data = {"success": True, "results": raw_data}
        else:
            raw_data = {"success": False}

    # ── VEH_TO_NUM: encorex API — show only vnum + mobile_no ──
    if mode == "VEH_TO_NUM":
        result_obj = raw_data.get("result", {}) if isinstance(raw_data, dict) else {}
        vnum   = str(result_obj.get("vnum", "")).strip()
        mobile = str(result_obj.get("mobile_no", "")).strip()
        if vnum or mobile:
            lines = []
            if vnum:   lines.append(f"🚘 *V Num:* `{vnum}`")
            if mobile: lines.append(f"📱 *Mobile Num:* `{mobile}`")
            result_body = "\n".join(lines)
            header_text = f"*{AVI_HEADER}*\n\n"
            footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
            full_msg    = header_text + result_body + footer_text
            try:
                sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                asyncio.create_task(auto_delete_message(sent, delay=20))
            except Exception:
                sent = await context.bot.send_message(chat_id=int(user_id), text=full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                asyncio.create_task(auto_delete_message(sent, delay=20))
            user_doc_fresh = cols["users"].find_one({"user_id": user_id})
            if get_effective_credits(cols, user_doc_fresh) < 999999.0:
                cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
        else:
            await wait_msg.edit_text(
                "╔══════════════════════════════╗\n"
                "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                "╚══════════════════════════════╝\n\n"
                "🔍 *Is vehicle number ke liye koi data nahi mila.*\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
        return

    # ── PAN: structured display ──
    if mode == "PAN":
        if isinstance(raw_data, dict) and raw_data.get("success"):
            fn       = str(raw_data.get("firstName", "") or "").strip()
            ln       = str(raw_data.get("lastName",  "") or "").strip()
            fullname = str(raw_data.get("fullName",  "") or "").strip()
            dob      = str(raw_data.get("dob",        "") or "").strip()
            status   = str(raw_data.get("panStatus",  "") or "").strip()
            lines    = []
            display_name = fullname or f"{fn} {ln}".strip()
            if display_name: lines.append(f"👤 *Full Name:* `{display_name}`")
            if fn and fn != display_name: lines.append(f"👤 *First Name:* `{fn}`")
            if ln and ln != display_name: lines.append(f"👤 *Last Name:* `{ln}`")
            if dob:    lines.append(f"📅 *DOB:* `{dob}`")
            if status: lines.append(f"💳 *PAN Status:* `{status}`")
            if lines:
                result_body = "\n".join(lines)
                header_text = f"*{AVI_HEADER}*\n\n"
                footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
                full_msg    = header_text + result_body + footer_text
                try:
                    sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                    asyncio.create_task(auto_delete_message(sent, delay=20))
                except Exception:
                    sent = await context.bot.send_message(chat_id=int(user_id), text=full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                    asyncio.create_task(auto_delete_message(sent, delay=20))
                user_doc_fresh = cols["users"].find_one({"user_id": user_id})
                if get_effective_credits(cols, user_doc_fresh) < 999999.0:
                    cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
            else:
                await wait_msg.edit_text(
                    "╔══════════════════════════════╗\n"
                    "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                    "╚══════════════════════════════╝\n\n"
                    "🔍 *Is PAN ke liye koi data nahi mila.*\n\n"
                    f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅",
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
        else:
            await wait_msg.edit_text(
                "╔══════════════════════════════╗\n"
                "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                "╚══════════════════════════════╝\n\n"
                "🔍 *Is PAN ke liye koi data nahi mila.*\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
        return

    # ── INSTAGRAM: plain text carry-forward (TG jaisa) ──
    if mode == "INSTAGRAM":
        # Plain text response
        if isinstance(raw_data, str):
            cleaned = clean_output(raw_data)
            cleaned = re.sub(r'@?Cyb3rS0ldier', '@avi5029', cleaned, flags=re.IGNORECASE)
        else:
            # JSON response — dump as text and clean
            cleaned = clean_output(json.dumps(raw_data, ensure_ascii=False, indent=2))
        if not cleaned.strip():
            await wait_msg.edit_text(
                "╔══════════════════════════════╗\n"
                "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                "╚══════════════════════════════╝\n\n"
                "🔍 *Is Instagram username ke liye koi data nahi mila.*\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                "_(Koi credit deduct nahi kiya gaya)_",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
            return
        header_text = f"*{AVI_HEADER}*\n\n"
        footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
        full_msg = header_text + cleaned + footer_text
        if len(full_msg) > 4000:
            full_msg = header_text + cleaned[:3900] + "\n\n_(truncated)_" + footer_text
        try:
            sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
        except Exception:
            try:
                sent = await wait_msg.edit_text(full_msg, reply_markup=back_to_main_keyboard())
            except Exception:
                sent = await context.bot.send_message(chat_id=int(user_id), text=full_msg, reply_markup=back_to_main_keyboard())
        asyncio.create_task(auto_delete_message(sent, delay=20))
        user_doc_fresh = cols["users"].find_one({"user_id": user_id})
        if get_effective_credits(cols, user_doc_fresh) < 999999.0:
            cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
        return

    # ── FREE FIRE: structured display ──
    if mode == "FREE_FIRE":
        if isinstance(raw_data, dict) and raw_data.get("status") == "success":
            SKIP_FF = {"status", "message", "developer", "channel", "Time", "Date"}
            lines   = []
            for k, v in raw_data.items():
                if k in SKIP_FF or not v:
                    continue
                val_str = str(v).strip()
                # Replace branding in values
                val_str = val_str.replace("AnkuCode", "@Avi5029").replace("TryByte || Ankucode", "@aviosint").replace("Ankucode", "@aviosint")
                lines.append(f"📌 *{k}:* `{val_str}`")
            lines.append(f"\n👨‍💻 *Developer:* `@Avi5029`")
            lines.append(f"📢 *Channel:* `@aviosint`")
            result_body = "\n".join(lines)
            header_text = f"*{AVI_HEADER}*\n\n"
            footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
            full_msg    = header_text + result_body + footer_text
            if len(full_msg) > 4000:
                full_msg = header_text + result_body[:3900] + "\n_(truncated)_" + footer_text
            try:
                sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                asyncio.create_task(auto_delete_message(sent, delay=20))
            except Exception:
                sent = await context.bot.send_message(chat_id=int(user_id), text=full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                asyncio.create_task(auto_delete_message(sent, delay=20))
            user_doc_fresh = cols["users"].find_one({"user_id": user_id})
            if get_effective_credits(cols, user_doc_fresh) < 999999.0:
                cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
        else:
            await wait_msg.edit_text(
                "╔══════════════════════════════╗\n"
                "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                "╚══════════════════════════════╝\n\n"
                "🔍 *Is UID ke liye koi Free Fire data nahi mila.*\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
        return

    # ── FAMILY / RATIONS_INFO mode — encorexapi: {"success":true, "data": {"members":[...], ...}} ──
    if mode in ("FAMILY", "RATIONS_INFO"):
        result_body = None
        try:
            top_success = raw_data.get("success", False)
            data_block  = raw_data.get("data", {}) if isinstance(raw_data.get("data"), dict) else {}
            members     = data_block.get("members", [])

            if top_success and isinstance(members, list) and members:
                sep = "━" * 30
                lines = []

                # ── Ration Card Info block ──
                card_type  = str(data_block.get("card_type",  "") or "").strip()
                district   = str(data_block.get("district",   "") or "").strip()
                state      = str(data_block.get("state",      "") or "").strip()
                fps_name   = str(data_block.get("fps_name",   "") or "").strip()
                scheme     = str(data_block.get("scheme",     "") or "").strip()
                issue_date = str(data_block.get("issue_date", "") or "").strip()
                rc_id      = str(data_block.get("ration_card_id", "") or
                                 raw_data.get("ration_card_number", "") or "").strip()
                address    = str(data_block.get("address",    "") or "").strip()

                lines.append(f"🪪 *RATION CARD INFO*")
                lines.append(f"`{sep}`")
                if rc_id:       lines.append(f"🔢 *Card No:*     `{rc_id}`")
                if card_type:   lines.append(f"🏷️ *Card Type:*   `{card_type}`")
                if scheme:      lines.append(f"📋 *Scheme:*      `{scheme}`")
                if district:    lines.append(f"🏙️ *District:*    `{district}`")
                if state:       lines.append(f"🗺️ *State:*       `{state}`")
                if fps_name:    lines.append(f"🏪 *FPS Name:*    `{fps_name}`")
                if issue_date:  lines.append(f"📅 *Issue Date:*  `{issue_date}`")
                if address:     lines.append(f"🏠 *Address:*     `{address}`")

                # ── Members block ──
                lines.append(f"\n👨‍👩‍👧 *FAMILY MEMBERS —* `{len(members)} found`")
                lines.append(f"`{sep}`")

                GENDER_MAP = {"M": "Male", "F": "Female", "O": "Other"}
                for i, m in enumerate(members, 1):
                    name         = str(m.get("member_name", "") or "").strip()
                    relationship = str(m.get("relationship", "") or "").strip()
                    gender_raw   = str(m.get("gender", "") or "").strip()
                    gender       = GENDER_MAP.get(gender_raw.upper(), gender_raw)
                    uid_masked   = str(m.get("uid_masked", "") or "").strip()

                    mem_parts = []
                    if name:         mem_parts.append(f"👤 *{name}*")
                    if relationship: mem_parts.append(f"🔗 {relationship}")
                    if gender:       mem_parts.append(f"⚧️ {gender}")
                    if uid_masked:   mem_parts.append(f"🆔 `{uid_masked}`")
                    lines.append(f"*{i}.* " + "  |  ".join(mem_parts))

                result_body = "\n".join(lines)

        except Exception as parse_err:
            logging.warning(f"FAMILY parse error: {parse_err}")

        if result_body:
            header_text = f"*{AVI_HEADER}*\n\n"
            footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
            full_msg    = header_text + result_body + footer_text
            if len(full_msg) > 4000:
                full_msg = header_text + result_body[:3900] + "\n_(truncated)_" + footer_text
            try:
                sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                asyncio.create_task(auto_delete_message(sent, delay=20))
            except Exception:
                sent = await context.bot.send_message(
                    chat_id=int(user_id), text=full_msg,
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                asyncio.create_task(auto_delete_message(sent, delay=20))
            user_doc_fresh = cols["users"].find_one({"user_id": user_id})
            if get_effective_credits(cols, user_doc_fresh) < 999999.0:
                cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
            return
        else:
            await wait_msg.edit_text(
                "╔══════════════════════════════╗\n"
                "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                "╚══════════════════════════════╝\n\n"
                "🔍 *Is Aadhaar ke liye koi family/ration data nahi mila.*\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                "_(Koi credit deduct nahi kiya gaya)_",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
            return

    # ── Unwrap nested response ──
    # pyosintx NUMBER/AADHAR: {"success":true, "data": {"count":N, "results":[...]}}
    # pyosintx TG/VEH_TO_NUM: {"success":true, "data": {...flat dict...}}
    # zx-osint VEHICLE:        {"success":true, "result": {"source1":{...}, "source2":{...}}}
    if isinstance(raw_data.get("data"), dict):
        inner = raw_data["data"]
        if isinstance(inner.get("results"), list):
            raw_data = {"success": True, "results": inner["results"]}
        else:
            # flat data dict (TG, VEH_TO_NUM)
            inner["success"] = True
            raw_data = inner
    elif isinstance(raw_data.get("data"), list) and raw_data.get("data"):
        raw_data = {"success": True, "results": raw_data["data"]}
    elif isinstance(raw_data.get("result"), dict):
        # zx-osint vehicle: keep raw_data as-is, VEHICLE handler below reads raw_data["result"]
        pass
    elif isinstance(raw_data.get("result"), list) and raw_data.get("result"):
        raw_data = {"success": True, "results": raw_data["result"]}

    # ── Validate success field ──
    if not raw_data or not raw_data.get("success"):
        err = raw_data.get("msg", raw_data.get("message", "")) if raw_data else ""
        # Check if "not found" in error message
        not_found_keywords = ["not found", "notfound", "no data", "no record", "no result", "not_found", "404"]
        is_not_found = any(kw in str(err).lower() for kw in not_found_keywords) or not err
        if is_not_found:
            await wait_msg.edit_text(
                "╔════════════════════════════╗\n"
                "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                "╚════════════════════════════╝\n\n"
                "🔍 *Is query ke liye koi data nahi mila.*\n\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                "_(Aapka credit wapas kar diya gaya — koi deduction nahi)_\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
        else:
            await wait_msg.edit_text(
                "╔════════════════════════════╗\n"
                "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                "╚════════════════════════════╝\n\n"
                f"📋 *Reason:* `{err}`\n\n"
                f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                "_(Koi credit deduct nahi kiya gaya)_",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
        return

    # ── VEHICLE/CAR: zx-osint API response handler ──
    # Response: {"success":true, "result": {"source1": {"details":{...}}, "source2": {"data": {"Nexus2":{...}}}}}
    if mode in ("VEHICLE", "CAR"):
        raw_result = raw_data.get("result") if isinstance(raw_data.get("result"), dict) else {}

        # Source1 details (primary)
        source1 = raw_result.get("source1", {})
        s1_details = source1.get("details", {}) if isinstance(source1, dict) else {}

        # Source2 → Nexus2 (secondary, richer in some fields)
        source2  = raw_result.get("source2", {})
        s2_data  = source2.get("data", {}) if isinstance(source2, dict) else {}
        nexus2   = s2_data.get("Nexus2", {}) if isinstance(s2_data, dict) else {}
        if not isinstance(nexus2, dict) or nexus2.get("error"):
            nexus2 = {}

        # Merge: source1 details as base, nexus2 fills gaps
        combined = {**s1_details, **{k: v for k, v in nexus2.items() if v and k not in s1_details}}

        # Strip "api_by" attribution if somehow present
        combined.pop("api_by", None)

        # Ordered display — zx-osint uses display-friendly camelCase/Title Case keys
        VEHICLE_FIELDS = [
            ("Owner Name",            "👤", "OWNER NAME"),
            ("Maker Model",           "🚘", "MODEL"),
            ("Vehicle Class",         "🏷️", "VEHICLE CLASS"),
            ("Fuel Type",             "⛽", "FUEL TYPE"),
            ("Fuel Norms",            "🔖", "FUEL NORMS"),
            ("Registration Date",     "📅", "REG DATE"),
            ("Registered RTO",        "🏛️", "RTO"),
            ("Address",               "🏠", "ADDRESS"),
            ("City Name",             "🏙️", "CITY"),
            ("Insurance Company",     "🛡️", "INSURANCE CO"),
            ("Insurance Upto",        "📅", "INS VALID"),
            ("Insurance Expiry",      "📅", "INS EXPIRY"),
            ("Fitness Upto",          "📅", "FITNESS UPTO"),
            ("PUC Upto",              "📅", "PUC VALID"),
            ("Tax Upto",              "💰", "TAX UPTO"),
            ("Financier Name",        "🏦", "FINANCER"),
            ("Phone",                 "📱", "PHONE"),
            # Nexus2 keys (fallbacks)
            ("Father's Name",         "👨", "FATHER NAME"),
            ("Model Name",            "🚘", "MODEL NAME"),
        ]

        SKIP_VALS = {"", "null", "none", "n/a", "na", "0", "false", "[]", "{}", "na"}
        seen_labels = set()
        lines = []

        for field_key, icon, label in VEHICLE_FIELDS:
            if label in seen_labels:
                continue
            val = combined.get(field_key)
            if val is None:
                continue
            val_str = str(val).strip()
            if val_str.lower() in SKIP_VALS:
                continue
            seen_labels.add(label)
            lines.append(f"{icon} *{label}:* `{val_str}`")

        if lines:
            result_body = "\n".join(lines)
        else:
            result_body = build_structured_form(filter_api_response(combined), do_mask=False) \
                          or f"`{json.dumps(combined, indent=2, ensure_ascii=False)}`"

        header_text = f"*{AVI_HEADER}*\n\n"
        footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
        full_msg    = header_text + result_body + footer_text
        if len(full_msg) > 4000:
            full_msg = header_text + result_body[:3900] + "\n\n_(truncated)_" + footer_text
        try:
            sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
            asyncio.create_task(auto_delete_message(sent, delay=20))
        except Exception:
            sent = await context.bot.send_message(
                chat_id=int(user_id), text=full_msg,
                parse_mode="Markdown", reply_markup=back_to_main_keyboard()
            )
            asyncio.create_task(auto_delete_message(sent, delay=20))
        user_doc_fresh = cols["users"].find_one({"user_id": user_id})
        fresh_credits  = get_effective_credits(cols, user_doc_fresh)
        if fresh_credits < 999999.0:
            cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
        return

    # ── Extract results list (handles both array and flat response) ──
    results_list = raw_data.get("results") if isinstance(raw_data.get("results"), list) else None

    if results_list:
        # Deduplicate by (NAME, MOBILE, circle) combo
        seen   = set()
        unique = []
        for entry in results_list:
            key = (
                str(entry.get("NAME", "") or entry.get("name", "")).strip().lower(),
                str(entry.get("MOBILE", "") or entry.get("mobile", "")).strip(),
                str(entry.get("circle", "")).strip().lower(),
            )
            if key not in seen:
                seen.add(key)
                unique.append(entry)

        # Apply branding replacements on the list
        try:
            s = json.dumps(unique, ensure_ascii=False)
            s = clean_output(s)
            unique = json.loads(s)
        except Exception:
            pass

        total_unique = len(unique)

        # Screen pe sirf Record #1 dikhao
        first_entry = unique[0]
        normalised  = {k.lower(): v for k, v in first_entry.items()}
        if mode == "NUMBER":
            first_block = build_ordered_form(normalised, NUMBER_FIELD_ORDER, do_mask=False)
        elif mode == "AADHAR":
            first_block = build_ordered_form(normalised, AADHAR_FIELD_ORDER, do_mask=False)
        elif mode == "TG_USERNAME":
            first_block = build_ordered_form(normalised, TG_FIELD_ORDER, do_mask=False)
        else:
            first_block = build_structured_form(normalised, do_mask=False)
        if not first_block:
            first_block = f"`{json.dumps(first_entry, indent=2, ensure_ascii=False)}`"

        result_body = f"📋 *RECORD #1*\n`{'─' * 36}`\n{first_block}"
        if total_unique > 1:
            result_body += f"\n\n📊 *Total Results: {total_unique}* _(All records in file below)_"

    else:
        # Flat dict response — TG_USERNAME, VEH_TO_NUM, or old-style
        # Check if API returned success:false (e.g. VEH_TO_NUM number not found)
        if not raw_data.get("success", True):
            err_msg = raw_data.get("msg", raw_data.get("message", ""))
            not_found_keywords = ["not found", "notfound", "no data", "no record", "no result", "not_found", "404"]
            is_not_found = any(kw in str(err_msg).lower() for kw in not_found_keywords) or not err_msg
            if is_not_found:
                await wait_msg.edit_text(
                    "╔══════════════════════════════╗\n"
                    "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                    "╚══════════════════════════════╝\n\n"
                    "🔍 *Is query ke liye koi data nahi mila.*\n\n"
                    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                    f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                    "_(Aapka credit wapas kar diya gaya — koi deduction nahi)_\n"
                    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
            else:
                await wait_msg.edit_text(
                    "╔══════════════════════════════╗\n"
                    "║   ⚠️  *DATA NOT AVAILABLE*  ⚠️   ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"📋 *Reason:* `{err_msg}`\n\n"
                    f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅\n"
                    "_(Koi credit deduct nahi kiya gaya)_",
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
            return

        filtered = filter_api_response(raw_data)
        # Also strip "msg" key from display (keep only data fields)
        filtered.pop("msg", None)
        try:
            filtered_str = json.dumps(filtered, ensure_ascii=False)
            filtered_str = clean_output(filtered_str)
            filtered     = json.loads(filtered_str)
        except Exception:
            pass

        if mode == "NUMBER":
            form_text = build_ordered_form(filtered, NUMBER_FIELD_ORDER, do_mask=False)
        elif mode == "AADHAR":
            form_text = build_ordered_form(filtered, AADHAR_FIELD_ORDER, do_mask=False)
        elif mode in ("TG_USERNAME", "VEH_TO_NUM"):
            form_text = build_ordered_form(filtered, TG_FIELD_ORDER, do_mask=False)
        else:
            form_text = build_structured_form(filtered, do_mask=False)
        if form_text:
            result_body = form_text
        else:
            result_body = f"`{json.dumps(filtered, indent=2, ensure_ascii=False)}`"

    # Telegram message limit guard
    header_text = f"*{AVI_HEADER}*\n\n"
    footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
    full_msg    = header_text + result_body + footer_text

    if len(full_msg) > 4000:
        full_msg = header_text + result_body[:3900] + "\n\n_(truncated — see file)_" + footer_text

    # ── Send result ──
    try:
        sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
        asyncio.create_task(auto_delete_message(sent, delay=20))
    except Exception:
        sent = await context.bot.send_message(
            chat_id=int(user_id), text=full_msg,
            parse_mode="Markdown", reply_markup=back_to_main_keyboard()
        )
        asyncio.create_task(auto_delete_message(sent, delay=20))

    # ── Also send professional txt file (auto-deletes in 20s) ──
    # TG_USERNAME aur VEH_TO_NUM ke liye txt file nahi chahiye — sirf text show karo
    if mode in ("TG_USERNAME", "VEH_TO_NUM"):
        user_doc_fresh = cols["users"].find_one({"user_id": user_id})
        fresh_credits  = get_effective_credits(cols, user_doc_fresh)
        if fresh_credits < 999999.0:
            cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
        return

    raw_export = unique if results_list else filtered
    # Build professional plain-text file (not raw JSON)
    if isinstance(raw_export, list):
        file_lines = [
            "=" * 48,
            f"  {AVI_HEADER}  —  FULL RESULT EXPORT",
            "=" * 48,
            f"  Total Records: {len(raw_export)}",
            "=" * 48,
            "",
        ]
        _txt_order = (
            NUMBER_FIELD_ORDER if mode == "NUMBER" else
            AADHAR_FIELD_ORDER if mode == "AADHAR" else
            TG_FIELD_ORDER     if mode == "TG_USERNAME" else
            None
        )
        for idx, entry in enumerate(raw_export, 1):
            file_lines.append(f"RECORD #{idx}")
            file_lines.append("-" * 40)
            entry_lower = {k.lower(): (k, v) for k, v in entry.items()}
            written = set()
            if _txt_order:
                for okey in _txt_order:
                    if okey in entry_lower:
                        orig_k, v = entry_lower[okey]
                        if v is None or str(v).strip() in ("", "null", "None", "N/A"):
                            continue
                        label = okey.upper().replace("_", " ")
                        file_lines.append(f"{label:<20}: {str(v).strip()}")
                        written.add(okey)
            for k, v in entry.items():
                if k.lower() in written:
                    continue
                if v is None or str(v).strip() in ("", "null", "None", "N/A"):
                    continue
                label = k.upper().replace("_", " ")
                file_lines.append(f"{label:<20}: {str(v).strip()}")
            file_lines.append("")
        file_lines.append("=" * 48)
        file_lines.append(f"  Powered by {AVI_HEADER}")
        file_lines.append("=" * 48)
        raw_text = "\n".join(file_lines)
    else:
        file_lines = [
            "=" * 48,
            f"  {AVI_HEADER}  —  RESULT EXPORT",
            "=" * 48,
            "",
        ]
        for k, v in raw_export.items():
            if v is None or str(v).strip() in ("", "null", "None", "N/A"):
                continue
            label = k.upper().replace("_", " ")
            file_lines.append(f"{label:<20}: {str(v).strip()}")
        file_lines.append("")
        file_lines.append("=" * 48)
        file_lines.append(f"  Powered by {AVI_HEADER}")
        file_lines.append("=" * 48)
        raw_text = "\n".join(file_lines)

    if len(raw_text) > 50:
        try:
            with tempfile.NamedTemporaryFile(
                mode='w', suffix='.txt', delete=False,
                prefix='result_', encoding='utf-8'
            ) as f:
                f.write(raw_text)
                tmp_path = f.name
            file_msg = await context.bot.send_document(
                chat_id=int(user_id),
                document=open(tmp_path, 'rb'),
                filename="full_result.txt",
                caption="📄 *Full Result Export*\n⚠️ This file will auto-delete in 20 seconds.",
                parse_mode="Markdown"
            )
            asyncio.create_task(auto_delete_message(file_msg, delay=20))
        except Exception:
            pass
        finally:
            try:
                _os.unlink(tmp_path)
            except Exception:
                pass

    # ── Deduct credits (only after confirmed success, only if not unlimited) ──
    user_doc_fresh = cols["users"].find_one({"user_id": user_id})
    fresh_credits  = get_effective_credits(cols, user_doc_fresh)
    if fresh_credits < 999999.0:
        cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})


# ========================================================
# =================== KEYBOARDS ==========================
# ========================================================

def main_menu_keyboard():
    keyboard = [
        [InlineKeyboardButton("📱 Number Info",           callback_data="NUMBER"),
         InlineKeyboardButton("🆔 Aadhar Info",           callback_data="AADHAR")],
        [InlineKeyboardButton("📞 TG USERNAME TO NUMBER", callback_data="TG_USERNAME")],
        [InlineKeyboardButton("🚘 VEHICLE TO NUMBER",     callback_data="VEH_TO_NUM")],
        [InlineKeyboardButton("🧺 Rations Info",          callback_data="RATIONS_INFO"),
         InlineKeyboardButton("🏛️ RTO INFO",              callback_data="VEHICLE")],
        [InlineKeyboardButton("PAK 🇵🇰 INFO",              callback_data="PAK_INFO"),
         InlineKeyboardButton("🚗 VEHICLE INFO",          callback_data="UPI_INFO")],
        [InlineKeyboardButton("💳 Fampay Info",           callback_data="SOON_10"),
         InlineKeyboardButton("👨‍👩‍👧 Aadhar to Family", callback_data="FAMILY")],
        [InlineKeyboardButton("🆔 PAN DETAILS",           callback_data="PAN"),
         InlineKeyboardButton("📲 Instagram OSINT",       callback_data="INSTAGRAM_OSINT")],
        [InlineKeyboardButton("📨 EMAIL LOOK UP",         callback_data="EMAIL_LOOKUP"),
         InlineKeyboardButton("💰 BUY CREDIT",            callback_data="BUY")],
        [InlineKeyboardButton("📍 Live Location",         callback_data="LIVE_LOCATION"),
         InlineKeyboardButton("🎁 Free Credit",           callback_data="FREE_CREDIT_MENU")],
        [InlineKeyboardButton("🔥 FREE FIRE LOOKUP",      callback_data="FREE_FIRE")],
        [InlineKeyboardButton("🤖 CREATE YOUR OWN BOT",  callback_data="BOT_SERVICE")],
        [InlineKeyboardButton("🛡️ PROTECT U NUMBER",     callback_data="PROTECT_SERVICE")],
        [InlineKeyboardButton("💬 Feedback",              callback_data="FEEDBACK"),
         InlineKeyboardButton("🐛 Bug Report",            callback_data="BUG_REPORT")],
    ]
    return InlineKeyboardMarkup(keyboard)


def back_to_main_keyboard():
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")]
    ])


def free_credit_menu_keyboard():
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("REFER&EARN 💰", callback_data="REFER")],
        [InlineKeyboardButton("CHECK-IN ✅",   callback_data="CHECKIN")],
        [InlineKeyboardButton("WATCH&EARN",    callback_data="WATCH_EARN")],
        [InlineKeyboardButton("🔙 Back",       callback_data="MAIN_MENU")],
    ])


def buy_plans_keyboard():
    """All credit plans + unlimited plans as buttons."""
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("💰 50 Credits — ₹249",            callback_data="PLAN_249")],
        [InlineKeyboardButton("💰 250 Credits — ₹500",           callback_data="PLAN_500")],
        [InlineKeyboardButton("💰 500 Credits — ₹999",           callback_data="PLAN_999")],
        [InlineKeyboardButton("♾️ Unlimited — ₹500 (7 Days)",       callback_data="PLAN_UNLIM_7")],
        [InlineKeyboardButton("♾️ Unlimited — ₹1,000 (30 Days)",    callback_data="PLAN_UNLIM_30")],
        [InlineKeyboardButton("♾️ Forever Unlimited — ₹1,999",      callback_data="PLAN_FOREVER")],
        [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
    ])

def buy_keyboard(dev_chat):
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("ADMIN 🧑‍💻", url=dev_chat)],
        [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
    ])


def cancel_keyboard():
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("❌ Cancel", callback_data="MAIN_MENU")]
    ])


# ========================================================
# ============= BOT FACTORY ==============================
# ========================================================

def make_handlers(cfg, cols=None):
    if cols is None:
        cols = get_db(cfg["DB_NAME"])

    ADMIN_ID      = int(cfg["ADMIN_ID"])
    ADMIN_ID_2    = int(cfg.get("ADMIN_ID_2", 0))   # Doosra admin (0 = disabled)
    FORCE_CHANNEL = cfg["FORCE_CHANNEL"]
    DEV_CHAT      = cfg["DEV_CHAT"]

    def get_all_admin_ids():
        """Dono admins ki list return karo (sirf valid ones)."""
        ids = [ADMIN_ID]
        if ADMIN_ID_2 and ADMIN_ID_2 != ADMIN_ID:
            ids.append(ADMIN_ID_2)
        return ids

    def is_any_admin(user_id: int) -> bool:
        """Check karo koi bhi admin hai."""
        return user_id in get_all_admin_ids()

    def build_home_text(user, user_doc):
        credits = get_effective_credits(cols, user_doc) if user_doc else 0.0
        now     = datetime.utcnow()

        # Check unlimited plan
        has_unlim   = False
        unlim_line  = ""
        if user_doc and user_doc.get("unlimited_plan"):
            unlim_exp = user_doc.get("unlimited_expires_at")
            if unlim_exp and unlim_exp > now:
                has_unlim  = True
                unlim_line = f"\n♾️ **Unlimited Plan Active!**\n⏰ **Expires:** {unlim_exp.strftime('%d %b %Y')}"

        show_credits = 0.0 if has_unlim else max(credits, 0.0)
        credit_display = "♾️ Unlimited" if has_unlim else str(round(show_credits, 1))

        # Expiry line — credit-based users ko expiry nahi dikhani
        safe_cr     = float(user_doc.get("safe_credits", 0.0)) if user_doc else 0.0
        expiry_line = ""
        safe_line = f"\n🎁 **Free Credits (No Expiry):** {round(safe_cr, 1)}" if safe_cr > 0 else ""
        return (
            f"♠️ ♠️ ♠️ ♠️ **Avi Osint** ♠️ ♠️ ♠️ ♠️\n\n"
            f"👤 **Name:** {user.first_name}\n"
            f"📝 **Chat id:** `{user.id}`\n"
            f"💳 **Available credit:** {credit_display}{expiry_line}{safe_line}{unlim_line}\n\n"
            f"🟢 *BOT STATUS: ONLINE*"
        )

    # ---------- START ----------
    async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
        user   = update.effective_user

        # ── Mirror bot stopped-by-parent-admin check ──
        # If this bot's token is in stopped_bots, it means parent admin stopped it.
        # Non-admin users get a blocked message; admin still gets through.
        if not is_any_admin(user.id) and cfg["BOT_TOKEN"] in stopped_bots:
            await update.message.reply_text(
                "🔴 *BOT STOPPED BY ADMIN*\n\n"
                "This bot has been stopped by the admin.\n"
                "Contact admin for access.\n\n"
                "📩 *CONTACT ADMIN FOR USE THIS BOT*",
                parse_mode="Markdown"
            )
            return

        ref_id = context.args[0] if context.args else None
        is_new_user = cols["users"].find_one({"user_id": str(user.id)}) is None
        init_user(cols, user.id, ref_id, context, username=user.username or "")
        user_doc = cols["users"].find_one({"user_id": str(user.id)})
        text     = build_home_text(user, user_doc)

        WELCOME_MSG = (
            "👋 *Welcome to AVI OSINT!*\n"
            "🕵️ _The most powerful OSINT bot — fully advanced._\n\n"
            "🔍 *What this bot does:*\n"
            "Look up mobile numbers, Aadhaar, vehicle numbers, "
            "Telegram usernames, and more.\n\n"
            "⚠️ *NOTE:*\n"
            "Searches require credits. Use /redeem or tap *💰 BUY CREDIT*.\n\n"
            "🎁 Earn free credits via Refer & Earn or daily Check-In.\n\n"
            "_This message will not appear again._"
        )

        if FORCE_CHANNEL:
            is_member = False
            check_failed = False
            try:
                m = await context.bot.get_chat_member(FORCE_CHANNEL, user.id)
                if m.status in ["member", "administrator", "creator"]:
                    is_member = True
            except Exception as e:
                logging.warning(f"[FORCE_CHANNEL] get_chat_member failed for {FORCE_CHANNEL} user {user.id}: {e}")
                check_failed = True
                is_member = False

            # Agar check hi fail ho gaya (bot ka issue) to user ko block mat karo
            if check_failed:
                await update.message.reply_text(text, parse_mode="Markdown", reply_markup=main_menu_keyboard())
                if is_new_user:
                    await context.bot.send_message(
                        chat_id=user.id,
                        text=WELCOME_MSG,
                        parse_mode="Markdown",
                        reply_markup=InlineKeyboardMarkup([
                            [InlineKeyboardButton("✅ Got it!", callback_data="MAIN_MENU")]
                        ])
                    )
            elif is_member:
                await update.message.reply_text(text, parse_mode="Markdown", reply_markup=main_menu_keyboard())
                # Show one-time welcome popup for brand-new users
                if is_new_user:
                    await context.bot.send_message(
                        chat_id=user.id,
                        text=WELCOME_MSG,
                        parse_mode="Markdown",
                        reply_markup=InlineKeyboardMarkup([
                            [InlineKeyboardButton("✅ Got it!", callback_data="MAIN_MENU")]
                        ])
                    )
            else:
                channel_username = FORCE_CHANNEL.lstrip('@')
                await update.message.reply_text(
                    f"📢 Pehle channel join karo!\n\nChannel: {FORCE_CHANNEL}",
                    reply_markup=InlineKeyboardMarkup([
                        [InlineKeyboardButton("🔗 Join Channel",
                                              url=f"https://t.me/{channel_username}")],
                        [InlineKeyboardButton("✅ Submit / Check", callback_data="CHECK_JOIN")]
                    ])
                )
        else:
            await update.message.reply_text(text, parse_mode="Markdown", reply_markup=main_menu_keyboard())
            # Show one-time welcome popup for brand-new users
            if is_new_user:
                await context.bot.send_message(
                    chat_id=user.id,
                    text=WELCOME_MSG,
                    parse_mode="Markdown",
                    reply_markup=InlineKeyboardMarkup([
                        [InlineKeyboardButton("✅ Got it!", callback_data="MAIN_MENU")]
                    ])
                )

    # ---------- ADD CREDIT / UNLIMITED PLAN ----------
    # Formats:
    #   /add userid amount day         → credit plan (e.g. /add 123 50 7)
    #   /add userid 10day              → unlimited for 10 days
    #   /add userid 3month             → unlimited for 3 months
    #   /add userid 1year              → unlimited for 1 year
    async def add_credit(update: Update, context: ContextTypes.DEFAULT_TYPE):
        caller_id = update.effective_user.id
        # Sub-admin check: stored per-bot in DB as {"user_id": "SUB_ADMINS", "ids": [...]}
        sub_admins_doc = cols["users"].find_one({"user_id": "SUB_ADMINS"})
        sub_admin_ids  = sub_admins_doc.get("ids", []) if sub_admins_doc else []
        is_sub_admin   = caller_id in sub_admin_ids
        if not is_any_admin(caller_id) and not is_sub_admin:
            return
        args = context.args
        try:
            if len(args) < 2:
                raise ValueError("Not enough args")
            target_id = args[0]

            # ── Detect unlimited plan format: e.g. "10day", "3month", "1year" ──
            duration_str = args[1].lower()
            unlimited_match = re.match(r'^(\d+)(day|month|year)s?$', duration_str)
            if unlimited_match:
                qty  = int(unlimited_match.group(1))
                unit = unlimited_match.group(2)
                if unit == "day":
                    expires_at = datetime.utcnow() + timedelta(days=qty)
                    label = f"{qty} Day(s)"
                elif unit == "month":
                    expires_at = datetime.utcnow() + timedelta(days=qty * 30)
                    label = f"{qty} Month(s)"
                else:  # year
                    expires_at = datetime.utcnow() + timedelta(days=qty * 365)
                    label = f"{qty} Year(s)"
                cols["users"].update_one(
                    {"user_id": str(target_id)},
                    {"$set": {
                        "unlimited_plan": True,
                        "unlimited_expires_at": expires_at,
                    }},
                    upsert=True
                )
                await update.message.reply_text(
                    f"♾️ *Unlimited Plan Activated!*\n\n"
                    f"👤 User: `{target_id}`\n"
                    f"⏱ Duration: `{label}`\n"
                    f"📅 Expires: `{expires_at.strftime('%d %b %Y, %H:%M')} UTC`",
                    parse_mode="Markdown"
                )
                return

            # ── Normal credit plan: /add userid amount days ──
            if len(args) < 3:
                raise ValueError("Not enough args for credit plan")
            amount     = float(args[1])
            days       = int(args[2])

            # BUG FIX: Existing expiry check karo — future mein hai toh us se extend karo
            existing_user = cols["users"].find_one({"user_id": str(target_id)})
            existing_expiry = existing_user.get("credits_expires_at") if existing_user else None
            now_utc = datetime.utcnow()
            new_expiry = now_utc + timedelta(days=days)
            # Take the later of existing future expiry vs new expiry (never shrink)
            if existing_expiry and existing_expiry > now_utc:
                expires_at = max(existing_expiry, new_expiry)
            else:
                expires_at = new_expiry

            cols["users"].update_one(
                {"user_id": str(target_id)},
                {
                    "$inc": {"credits": amount},
                    "$set": {"credits_expires_at": expires_at}
                },
                upsert=True
            )
            await update.message.reply_text(
                f"✅ *Credits Added!*\n\n"
                f"👤 User: `{target_id}`\n"
                f"💳 Credits: `{amount}`\n"
                f"⏰ Valid for: `{days}` day(s)\n"
                f"📅 Expires: `{expires_at.strftime('%d %b %Y')}`",
                parse_mode="Markdown"
            )
        except Exception:
            await update.message.reply_text(
                "❌ *Usage:*\n\n"
                "Credit plan: `/add userid amount days`\n"
                "Ex: `/add 123456 50 7`\n\n"
                "Unlimited plan: `/add userid <N>day` or `<N>month` or `<N>year`\n"
                "Ex: `/add 123456 10day`\n"
                "Ex: `/add 123456 3month`\n"
                "Ex: `/add 123456 1year`",
                parse_mode="Markdown"
            )

    # ---------- REMOVE SUBSCRIPTION ----------
    async def remove_subscription(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        args = context.args
        if not args:
            await update.message.reply_text(
                "❌ *Usage:*\n\n`/remove <userid>`\n\nEx: `/remove 123456789`\n\n"
                "Ye command user ka unlimited plan aur sabhi admin-added credits remove kar dega.",
                parse_mode="Markdown"
            )
            return
        target_id = str(args[0]).strip()
        user_doc = cols["users"].find_one({"user_id": target_id})
        if not user_doc:
            await update.message.reply_text(
                f"❌ User `{target_id}` not found in database.",
                parse_mode="Markdown"
            )
            return
        cols["users"].update_one(
            {"user_id": target_id},
            {"$set": {
                "credits": 0.0,
                "credits_expires_at": None,
                "unlimited_plan": False,
                "unlimited_expires_at": None,
            }}
        )
        await update.message.reply_text(
            f"✅ *Subscription Removed!*\n\n"
            f"👤 User: `{target_id}`\n"
            f"💳 Credits: `0`\n"
            f"♾️ Unlimited Plan: `Removed`\n\n"
            f"_(Safe credits / refer credits preserved)_",
            parse_mode="Markdown"
        )

    # ---------- STATS ----------
    async def stats_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        total = cols["users"].count_documents({})
        await update.message.reply_text(f"📊 Bot Stats\n\n👥 Total Users: {total}")

    # ---------- CHECK — users with any credits or unlimited plan ----------
    async def check_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        now         = datetime.utcnow()
        MASS_EXPIRY = datetime(2027, 12, 31, 23, 59, 59)

        all_users  = list(cols["users"].find({}))
        rich_users = []
        fixed_count = 0

        for u in all_users:
            uid_str = u.get("user_id", "")
            if not uid_str or not str(uid_str).lstrip("-").isdigit():
                continue

            unlimited = u.get("unlimited_plan", False)
            unlim_exp = u.get("unlimited_expires_at")
            has_unlim = bool(unlimited and unlim_exp and unlim_exp > now)

            credits = float(u.get("credits", 0.0))
            cr_exp  = u.get("credits_expires_at")

            if credits > 0 and not has_unlim and cr_exp is None:
                cols["users"].update_one(
                    {"user_id": uid_str},
                    {"$set": {"credits_expires_at": MASS_EXPIRY}}
                )
                cr_exp = MASS_EXPIRY
                fixed_count += 1

            has_cr = credits > 0 and cr_exp and cr_exp > now

            if has_unlim or has_cr:
                rich_users.append((u, has_unlim, unlim_exp, credits, cr_exp))

        if not rich_users:
            await update.message.reply_text(
                "📭 *Koi active user nahi mila.*\n\nKisi ke paas bhi abhi credits ya unlimited plan nahi hai.",
                parse_mode="Markdown"
            )
            return

        uid_to_name = {}
        for (u, *_) in rich_users:
            uid_str = u.get("user_id", "")
            uid_to_name[uid_str] = u.get("username") or "—"

        lines = [
            "╔══════════════════════════════════╗",
            "║   💎  𝗔𝗖𝗧𝗜𝗩𝗘 𝗨𝗦𝗘𝗥𝗦 𝗥𝗘𝗣𝗢𝗥𝗧  💎   ║",
            "╚══════════════════════════════════╝\n",
        ]
        if fixed_count > 0:
            lines.append(f"🔧 *{fixed_count} users ki expiry fix karke default set ki gai.*\n")

        for i, (u, has_unlim, unlim_exp, credits, cr_exp) in enumerate(rich_users, 1):
            uid_str   = u.get("user_id", "?")
            uname     = u.get("username") or ""
            # Escape backticks so Markdown code-span doesn't break
            safe_name  = (uid_to_name.get(uid_str, "—") or "—").replace("`", "").replace("*", "").replace("_", "\\_")
            safe_uname = (f"@{uname}" if uname else "—").replace("`", "").replace("*", "").replace("_", "\\_")

            if has_unlim:
                credit_str = "Unlimited"
                exp_str    = unlim_exp.strftime('%d %b %Y') if unlim_exp else "—"
            else:
                credit_str = str(round(credits, 1))
                exp_str    = cr_exp.strftime('%d %b %Y') if cr_exp else "No expiry"

            lines.append(
                f"┌─ #{i}\n"
                f"│ 👤 Name/User: `{safe_name}`\n"
                f"│ 📝 Username: `{safe_uname}`\n"
                f"│ 🆔 User ID: `{uid_str}`\n"
                f"│ 💳 Credits: `{credit_str}`\n"
                f"│ ⏰ Expiry: `{exp_str}`\n"
                f"└──────────────────────"
            )

        lines.append(f"\n📊 *Total Active: {len(rich_users)}*")
        msg = "\n".join(lines)

        # Split if too long
        if len(msg) > 4000:
            chunks = [msg[i:i+4000] for i in range(0, len(msg), 4000)]
            for chunk in chunks:
                try:
                    await update.message.reply_text(chunk, parse_mode="Markdown")
                except Exception:
                    await update.message.reply_text(chunk)
        else:
            try:
                await update.message.reply_text(msg, parse_mode="Markdown")
            except Exception:
                await update.message.reply_text(msg)

    # ---------- STOP — bot under maintenance ----------
    async def stop_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        cols["users"].update_one(
            {"user_id": "BOT_SYSTEM_STATUS"},
            {"$set": {"bot_stopped": True}},
            upsert=True
        )
        await update.message.reply_text(
            "🔴 *Bot stopped!*\n\nUsers ko 'Under Maintenance' message milega.\nWapas start karne ke liye `/run` bhejo.",
            parse_mode="Markdown"
        )

    # ---------- RUN — bot maintenance khatam ----------
    async def run_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        cols["users"].update_one(
            {"user_id": "BOT_SYSTEM_STATUS"},
            {"$set": {"bot_stopped": False}},
            upsert=True
        )
        await update.message.reply_text(
            "🟢 *Bot is now LIVE!*\n\nSaare users ab normally use kar sakte hain.",
            parse_mode="Markdown"
        )

    # ---------- BROADCAST (own users + mirror bots ke users) ----------
    async def broadcast_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        reply_msg = update.message.reply_to_message
        if not reply_msg:
            await update.message.reply_text(
                "📢 *Broadcast karne ka tarika:*\n\n"
                "Jis message ko broadcast karna ho usse *reply* karo aur /broadcast bhejo.\n\n"
                "✅ Bold, italic, spoiler, mono — sab styles preserve honge.\n"
                "📌 Message ke upar auto *ADMIN BROADCAST* header lagega.\n"
                "🤖 Mirror bots ke users ko bhi broadcast jayega!",
                parse_mode="Markdown"
            )
            return

        from telegram import MessageEntity

        header     = "╔═══════════════════════════╗\n║  📢  ᴀᴅᴍɪɴ ʙʀᴏᴀᴅᴄᴀꜱᴛ  📢  ║\n╚═══════════════════════════╝\n\n"
        header_len = len(header.encode('utf-16-le')) // 2  # Telegram counts UTF-16 code units
        orig_text  = reply_msg.text or reply_msg.caption or ""
        orig_ents  = list(reply_msg.entities or reply_msg.caption_entities or [])

        shifted = []
        for ent in orig_ents:
            try:
                shifted.append(MessageEntity(
                    type=ent.type, offset=ent.offset + header_len, length=ent.length,
                    url=getattr(ent, "url", None), user=getattr(ent, "user", None),
                    language=getattr(ent, "language", None),
                ))
            except Exception:
                pass

        full_text = header + orig_text

        # ── Collect users from this bot's own DB ──
        own_users = list(cols["users"].find({}, {"user_id": 1}))

        # ── Collect mirror bot tokens recursively ──
        all_mirror_tokens = get_all_mirror_bots_recursive(cfg["BOT_TOKEN"])

        # ── Build send map: token → (bot_token_str, [user_ids]) ──
        # We send via direct Telegram Bot API (aiohttp) using each bot's own token
        # This is the most reliable method — no dependency on app.bot state
        send_map = {}  # key: token_str, value: (token_str, [uid_strings])

        # Main bot users — non-numeric IDs (BOT_SYSTEM_STATUS etc.) filter karo
        main_uids = [
            u["user_id"] for u in own_users
            if u.get("user_id") and str(u["user_id"]).lstrip("-").isdigit()
        ]
        if main_uids:
            send_map[cfg["BOT_TOKEN"]] = (cfg["BOT_TOKEN"], main_uids)

        # Mirror bot users — use each mirror bot's own token to send
        for m_token in all_mirror_tokens:
            m_db_name = stable_db_name(m_token)
            m_cols    = get_db(m_db_name)
            m_users   = list(m_cols["users"].find({}, {"user_id": 1}))
            m_uids    = [
                u["user_id"] for u in m_users
                if u.get("user_id") and str(u["user_id"]).lstrip("-").isdigit()
            ]
            if m_uids:
                send_map[m_token] = (m_token, m_uids)

        # Count total unique users
        all_uid_set = set()
        for _, (_, uids) in send_map.items():
            all_uid_set.update(uids)
        total   = len(all_uid_set)
        success = 0
        failed  = 0

        status_msg = await update.message.reply_text(
            f"📤 Broadcasting to {total} users ({len(all_mirror_tokens)} mirror bot(s) included)..."
        )

        async def send_via_api(bot_token: str, uid: str):
            """Send message directly via Telegram Bot API using aiohttp — most reliable."""
            nonlocal success, failed
            # BOT_SYSTEM_STATUS ya non-numeric user_id skip karo
            try:
                chat_id = int(uid)
            except (ValueError, TypeError):
                failed += 1
                return
            try:
                url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
                payload = {
                    "chat_id": chat_id,
                    "text":    full_text,
                }
                if shifted:
                    entities_json = []
                    for e in shifted:
                        raw_type = e.type
                        # PTB v20: e.type is already a string like "bold", "italic", etc.
                        # Older PTB: might be MessageEntityType enum → convert to string
                        if hasattr(raw_type, 'value'):
                            type_str = raw_type.value.lower()
                        elif isinstance(raw_type, str):
                            # Handle "MessageEntityType.BOLD" style (older PTB)
                            type_str = raw_type.split(".")[-1].lower() if "." in raw_type else raw_type.lower()
                        else:
                            type_str = str(raw_type).split(".")[-1].lower()
                        ent_dict = {"type": type_str, "offset": e.offset, "length": e.length}
                        if getattr(e, 'url', None):
                            ent_dict["url"] = e.url
                        entities_json.append(ent_dict)
                    payload["entities"] = entities_json
                async with aiohttp.ClientSession() as session:
                    async with session.post(
                        url, json=payload,
                        timeout=aiohttp.ClientTimeout(total=10)
                    ) as resp:
                        data = await resp.json()
                        if data.get("ok"):
                            success += 1
                        else:
                            failed += 1
                            logging.warning(f"Broadcast fail uid={uid}: {data.get('description')}")
            except Exception as ex:
                failed += 1
                logging.warning(f"Broadcast exception uid={uid}: {ex}")

        # Send in batches per bot — rate limit safe (30 msgs per batch, 0.05s delay)
        for bot_token_key, (bot_token_str, uids) in send_map.items():
            for i in range(0, len(uids), 30):
                batch = uids[i:i+30]
                await asyncio.gather(*[send_via_api(bot_token_str, uid) for uid in batch])
                await asyncio.sleep(0.05)

        await status_msg.edit_text(
            f"✅ *Broadcast Complete!*\n\n"
            f"👥 Total: `{total}`\n✅ Sent: `{success}`\n❌ Failed: `{failed}`\n"
            f"🤖 Mirror Bots (total tree): `{len(all_mirror_tokens)}`",
            parse_mode="Markdown"
        )

    # ---------- MYBOTS (mirror bot management — inline buttons) ----------
    async def mybots_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        await show_mirror_bot_list(update.message, context, reply=True)

    async def show_mirror_bot_list(msg_obj, context, reply=False, edit=False):
        mirror_tokens = get_mirror_bots_for_parent(cfg["BOT_TOKEN"])
        if not mirror_tokens:
            text = "🤖 *Koi mirror bot nahi chal raha abhi.*\n\nPehle koi user bot banana chahiye."
            kb   = back_to_main_keyboard()
            if edit:
                try:
                    await msg_obj.edit_text(text, parse_mode="Markdown", reply_markup=kb)
                except Exception:
                    await msg_obj.reply_text(text, parse_mode="Markdown", reply_markup=kb)
            else:
                await msg_obj.reply_text(text, parse_mode="Markdown", reply_markup=kb)
            return

        lines = ["🤖 *Mirror Bot Management*\n━━━━━━━━━━━━━━━━━━━━\n"]
        keyboard = []
        for i, tok in enumerate(mirror_tokens, 1):
            # Check if running or stopped
            if tok in user_bots:
                info  = user_bots.get(tok, {})
                uname = info.get("username", "unknown") if isinstance(info, dict) else "unknown"
                status_icon = "🟢"
                status_label = "Running"
            elif tok in stopped_bots:
                info  = stopped_bots.get(tok, {})
                uname = info.get("username", "unknown") if isinstance(info, dict) else "unknown"
                status_icon = "🔴"
                status_label = "Stopped"
            else:
                uname = "unknown"
                status_icon = "⚫"
                status_label = "Unknown"
            lines.append(f"{i}. {status_icon} @{uname} — *{status_label}*")
            tok_prefix = tok[:15]
            keyboard.append([
                InlineKeyboardButton(f"🤖 @{uname}", callback_data=f"MBOT_INFO_{tok_prefix}"),
                InlineKeyboardButton("🟢 Start",   callback_data=f"MBOT_START_{tok_prefix}"),
                InlineKeyboardButton("🔴 Stop",    callback_data=f"MBOT_STOP_{tok_prefix}"),
                InlineKeyboardButton("🗑️ Remove",  callback_data=f"MBOT_DEL_{tok_prefix}"),
            ])
        keyboard.append([InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")])

        text = "\n".join(lines)
        kb   = InlineKeyboardMarkup(keyboard)
        if edit:
            try:
                await msg_obj.edit_text(text, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await msg_obj.reply_text(text, parse_mode="Markdown", reply_markup=kb)
        else:
            await msg_obj.reply_text(text, parse_mode="Markdown", reply_markup=kb)

    # ---------- PROTECT ----------
    async def protect_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            await update.message.reply_text("❌ Only admin can use this.")
            return
        if not context.args:
            protected = list(cols["protect"].find({}, {"_id": 0, "value": 1}))
            if not protected:
                await update.message.reply_text(
                    "🛡️ No protected inputs yet.\n\nUsage:\n/protect <number or username>\n/unprotect <number or username>"
                )
                return
            vals = "\n".join(f"• {p['value']}" for p in protected)
            await update.message.reply_text(f"🛡️ *Protected Inputs:*\n\n{vals}", parse_mode="Markdown")
            return
        raw_val = " ".join(context.args).strip().lower().lstrip('@')

        # ── Protect in this bot's own DB ──
        if not cols["protect"].find_one({"value": raw_val}):
            cols["protect"].insert_one({"value": raw_val})

        # ── Propagate protect to ALL main bots (BOTS list) ──
        for b_cfg in BOTS:
            if b_cfg["BOT_TOKEN"] != cfg["BOT_TOKEN"]:
                try:
                    b_cols = get_db(b_cfg["DB_NAME"])
                    if not b_cols["protect"].find_one({"value": raw_val}):
                        b_cols["protect"].insert_one({"value": raw_val})
                except Exception:
                    pass

        # ── Propagate protect to ALL mirror bots recursively (all 3 main bot trees) ──
        all_tokens = set()
        for b_cfg in BOTS:
            for m_tok in get_all_mirror_bots_recursive(b_cfg["BOT_TOKEN"]):
                all_tokens.add(m_tok)
        for m_tok in all_tokens:
            try:
                m_db_name = stable_db_name(m_tok)
                m_cols    = get_db(m_db_name)
                if not m_cols["protect"].find_one({"value": raw_val}):
                    m_cols["protect"].insert_one({"value": raw_val})
            except Exception:
                pass

        await update.message.reply_text(
            f"🛡️ *Protected (Tree-Wide):* `{raw_val}`\n\nKoi bhi is input ko kisi bhi bot mein search nahi kar sakta.",
            parse_mode="Markdown"
        )

    # ---------- UNPROTECT ----------
    async def unprotect_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            await update.message.reply_text("❌ Only admin can use this.")
            return
        if not context.args:
            await update.message.reply_text("Usage: /unprotect <value>")
            return
        raw_val = " ".join(context.args).strip().lower().lstrip('@')

        # ── Remove from this bot's own DB ──
        result = cols["protect"].delete_one({"value": raw_val})

        # ── Remove from ALL main bots (BOTS list) ──
        for b_cfg in BOTS:
            if b_cfg["BOT_TOKEN"] != cfg["BOT_TOKEN"]:
                try:
                    b_cols = get_db(b_cfg["DB_NAME"])
                    b_cols["protect"].delete_one({"value": raw_val})
                except Exception:
                    pass

        # ── Remove from ALL mirror bots recursively ──
        all_tokens = set()
        for b_cfg in BOTS:
            for m_tok in get_all_mirror_bots_recursive(b_cfg["BOT_TOKEN"]):
                all_tokens.add(m_tok)
        for m_tok in all_tokens:
            try:
                m_db_name = stable_db_name(m_tok)
                m_cols    = get_db(m_db_name)
                m_cols["protect"].delete_one({"value": raw_val})
            except Exception:
                pass

        if result.deleted_count:
            await update.message.reply_text(
                f"✅ Protection removed (Tree-Wide): `{raw_val}`", parse_mode="Markdown"
            )
        else:
            await update.message.reply_text(
                f"❌ `{raw_val}` not found in protected list.", parse_mode="Markdown"
            )

    # ---------- REDEEM ----------
    async def redeem_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        import secrets, string
        user_id = update.effective_user.id
        args    = context.args

        if is_any_admin(user_id):
            if len(args) < 2:
                await update.message.reply_text(
                    "❌ Admin Usage:\n`/redeem <count> <credit_value>`\n\nEx: `/redeem 10 5`\n"
                    "(10 codes generate honge, har code = 5 credits)",
                    parse_mode="Markdown"
                )
                return
            try:
                count = int(args[0])
                value = float(args[1])
            except ValueError:
                await update.message.reply_text("❌ Count aur value number mein do.", parse_mode="Markdown")
                return
            if count < 1 or count > 100:
                await update.message.reply_text("❌ Count 1 se 100 ke beech hona chahiye.")
                return
            alphabet = string.ascii_uppercase + string.digits
            codes    = []
            for _ in range(count):
                code = ''.join(secrets.choice(alphabet) for _ in range(12))
                cols["redeem"].insert_one({
                    "code": code, "value": value,
                    "used": False, "used_by": None, "created_at": datetime.utcnow()
                })
                codes.append(code)
            codes_text = "\n".join(f"`{c}`" for c in codes)
            await update.message.reply_text(
                f"✅ *{count} Redeem Codes Generated!*\n"
                f"💰 *Each Code Value:* `{value}` Credits\n\n*Codes:*\n{codes_text}",
                parse_mode="Markdown"
            )
            return

        if len(args) < 1:
            await update.message.reply_text(
                "❌ Usage: `/redeem <code>`\n\nEx: `/redeem ABC123DEF456`", parse_mode="Markdown"
            )
            return

        code     = args[0].strip().upper()
        u_id_str = str(user_id)
        record   = cols["redeem"].find_one({"code": code})

        if not record:
            await update.message.reply_text("❌ Invalid redeem code! Please check and try again.")
            return
        if record.get("used"):
            await update.message.reply_text("❌ Ye code pehle se use ho chuka hai!")
            return

        value = record.get("value", 0.0)
        # Redeem credits ki expiry: 1 year from now
        redeem_expiry = datetime.utcnow() + timedelta(days=365)
        cols["redeem"].update_one(
            {"code": code},
            {"$set": {"used": True, "used_by": u_id_str, "used_at": datetime.utcnow()}}
        )
        # Credits add karo + expiry set karo (increment credits, update expiry to max of existing or new)
        existing_user = cols["users"].find_one({"user_id": u_id_str})
        existing_expiry = existing_user.get("credits_expires_at") if existing_user else None
        # Use whichever expiry is later
        if existing_expiry and existing_expiry > redeem_expiry:
            new_expiry = existing_expiry
        else:
            new_expiry = redeem_expiry
        cols["users"].update_one(
            {"user_id": u_id_str},
            {
                "$inc": {"credits": value},
                "$set": {"credits_expires_at": new_expiry}
            },
            upsert=True
        )
        updated  = cols["users"].find_one({"user_id": u_id_str})
        new_bal  = get_effective_credits(cols, updated)

        await update.message.reply_text(
            f"✅ *Redeem Successful!*\n\n"
            f"🎁 *Credits Added:* `{int(value)}`\n"
            f"💳 *New Balance:* `{round(new_bal, 1)}`",
            parse_mode="Markdown"
        )

    # ========================================================
    # =================== BUTTON HANDLER =====================
    # ========================================================
    async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
        q        = update.callback_query
        u_id     = str(q.from_user.id)
        user_doc = cols["users"].find_one({"user_id": u_id})
        await q.answer()

        # ── Mirror bot stopped-by-parent-admin check ──
        if not is_any_admin(q.from_user.id) and cfg["BOT_TOKEN"] in stopped_bots:
            try:
                await q.message.edit_text(
                    "🔴 *BOT STOPPED BY ADMIN*\n\n"
                    "This bot has been stopped by the admin.\n"
                    "📩 *CONTACT ADMIN FOR USE THIS BOT*",
                    parse_mode="Markdown"
                )
            except Exception:
                await q.answer("🔴 BOT STOPPED BY ADMIN. Contact admin.", show_alert=True)
            return

        # ── Maintenance check (skip for admin) ──
        # Sirf API-wale buttons pe show karo, aur sirf unhe jo >= 2 credit rakhte hain
        API_BUTTONS_WITH_MAINTENANCE = [
            "NUMBER", "AADHAR", "TG_USERNAME", "VEH_TO_NUM",
            "VEHICLE", "CAR", "FAMILY", "PAK_INFO",
            "PINCODE", "IFSC", "PAN", "EMAIL_LOOKUP",
            "INSTAGRAM_OSINT", "FREE_FIRE",
        ]
        if (not is_any_admin(q.from_user.id)
                and is_bot_stopped(cols)
                and q.data in API_BUTTONS_WITH_MAINTENANCE):
            u_credits_maint = get_effective_credits(cols, user_doc)
            if u_credits_maint >= 2:
                # Enough credit hai — maintenance dikhao
                try:
                    await q.message.edit_text(
                        "🔧 *Bot Under Maintenance*\n\nPlease try again after some time.",
                        parse_mode="Markdown",
                        reply_markup=back_to_main_keyboard()
                    )
                except Exception:
                    await q.answer("🔧 Bot Under Maintenance. Try again after some time.", show_alert=True)
                return
            # Credit nahi hai — maintenance mat dikhao, normal flow chalega (insufficient credit screen aayega)

        # ── MAIN_MENU ──
        if q.data == "MAIN_MENU":
            context.user_data.pop('wait_for', None)
            home_text = build_home_text(q.from_user, user_doc)
            try:
                await q.message.edit_text(home_text, parse_mode="Markdown", reply_markup=main_menu_keyboard())
            except Exception:
                await q.message.reply_text(home_text, parse_mode="Markdown", reply_markup=main_menu_keyboard())

        # ── CHECK_JOIN ──
        elif q.data == "CHECK_JOIN":
            is_member = False
            try:
                m = await context.bot.get_chat_member(FORCE_CHANNEL, q.from_user.id)
                if m.status in ["member", "administrator", "creator"]:
                    is_member = True
            except Exception:
                is_member = False

            if is_member:
                is_new_user = cols["users"].find_one({"user_id": u_id}) is None
                u = init_user(cols, q.from_user.id)
                home_text = build_home_text(q.from_user, u)
                await q.message.edit_text(home_text, parse_mode="Markdown", reply_markup=main_menu_keyboard())
                if is_new_user:
                    WELCOME_MSG = (
                        "👋 *Welcome to AVI OSINT!*\n"
                        "🕵️ _The most powerful OSINT bot — fully advanced._\n\n"
                        "🔍 *What this bot does:*\n"
                        "Look up mobile numbers, Aadhaar, vehicle numbers, "
                        "Telegram usernames, and more.\n\n"
                        "⚠️ *NOTE:*\n"
                        "Searches require credits. Use /redeem or tap *💰 BUY CREDIT*.\n\n"
                        "🎁 Earn free credits via Refer & Earn or daily Check-In.\n\n"
                        "_This message will not appear again._"
                    )
                    try:
                        await context.bot.send_message(
                            chat_id=q.from_user.id,
                            text=WELCOME_MSG,
                            parse_mode="Markdown",
                            reply_markup=InlineKeyboardMarkup([
                                [InlineKeyboardButton("✅ Got it!", callback_data="MAIN_MENU")]
                            ])
                        )
                    except Exception:
                        pass
            else:
                await q.answer("❌ Abhi join nahi kiya! Pehle channel join karo.", show_alert=True)

        # ── BUY CREDIT ──
        elif q.data == "BUY":
            buy_text = (
                "💳 *Plan Choose Karo*\n\n"
                "🔍 2 Credits = 1 Normal Search\n"
                "🔍 10 Credits = 1 TG Username / Vehicle Search\n\n"
                "👇 *Apna plan tap karo:*"
            )
            kb = InlineKeyboardMarkup([
                [InlineKeyboardButton("💰 50 Credits — ₹249",        callback_data="PLAN_249")],
                [InlineKeyboardButton("💰 250 Credits — ₹500",       callback_data="PLAN_500")],
                [InlineKeyboardButton("💰 500 Credits — ₹999",       callback_data="PLAN_999")],
                [InlineKeyboardButton("♾️ Unlimited — ₹500 (7 Days)",     callback_data="PLAN_UNLIM_7")],
                [InlineKeyboardButton("♾️ Unlimited — ₹1,000 (30 Days)",  callback_data="PLAN_UNLIM_30")],
                [InlineKeyboardButton("♾️ Forever Unlimited — ₹1,999",    callback_data="PLAN_FOREVER")],
                [InlineKeyboardButton("🧑‍💻 Admin Se Contact Karo", url=DEV_CHAT)],
                [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
            ])
            try:
                await q.message.edit_text(buy_text, reply_markup=kb, parse_mode="Markdown")
            except Exception:
                await q.message.reply_text(buy_text, reply_markup=kb, parse_mode="Markdown")

        # ── FREE CREDIT MENU ──
        elif q.data == "FREE_CREDIT_MENU":
            txt = "🎁 *Free Credit Options*\n\nNeeche se option choose karo:"
            try:
                await q.message.edit_text(txt, reply_markup=free_credit_menu_keyboard(), parse_mode="Markdown")
            except Exception:
                await q.message.reply_text(txt, reply_markup=free_credit_menu_keyboard(), parse_mode="Markdown")

        # ── FEEDBACK ──
        elif q.data == "FEEDBACK":
            context.user_data['wait_for'] = "FEEDBACK_TEXT"
            txt = (
                "💬 *Feedback / Suggestion*\n\n"
                "Aapka suggestion ya feedback likho — seedha admin tak pahunchega.\n\n"
                "📝 *Kuch bhi likh sakte ho:*\n"
                "• Naye features ki request\n"
                "• Bot improvement ideas\n"
                "• Koi bhi suggestion\n\n"
                "_Apna message niche bhejo:_"
            )
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await context.bot.send_message(chat_id=q.from_user.id, text=txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── BUG REPORT ──
        elif q.data == "BUG_REPORT":
            context.user_data['wait_for'] = "BUG_REPORT_TEXT"
            txt = (
                "🐛 *Bug Report*\n\n"
                "Koi issue ya bug mila? Detail mein batao — admin fix karega.\n\n"
                "📋 *Batao:*\n"
                "• Kya kiya tha (e.g. number search kiya)\n"
                "• Kya hua (e.g. error aaya / wrong result)\n"
                "• Kab hua (approximate time)\n\n"
                "_Apna bug report niche bhejo:_"
            )
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await context.bot.send_message(chat_id=q.from_user.id, text=txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── PLAN SELECTION → Show details + QR ──
        elif q.data.startswith("PLAN_"):
            # Plan data mapping
            PLAN_DATA = {
                "PLAN_249":      {"credits": "50 Credits",       "price": "₹249",   "validity": "7 Days",           "amount": 249,  "is_unlim": False, "is_forever": False},
                "PLAN_500":      {"credits": "250 Credits",      "price": "₹500",   "validity": "30 Days",          "amount": 500,  "is_unlim": False, "is_forever": False},
                "PLAN_999":      {"credits": "500 Credits",      "price": "₹999",   "validity": "30 Days",          "amount": 999,  "is_unlim": False, "is_forever": False},
                "PLAN_UNLIM_7":  {"credits": "Unlimited",        "price": "₹500",   "validity": "7 Days",           "amount": 500,  "is_unlim": True,  "is_forever": False},
                "PLAN_UNLIM_30": {"credits": "Unlimited",        "price": "₹1,000", "validity": "30 Days",          "amount": 1000, "is_unlim": True,  "is_forever": False},
                "PLAN_FOREVER":  {"credits": "Lifetime Unlimited","price": "₹1,999","validity": "Forever (500 Yrs)","amount": 1999, "is_unlim": True,  "is_forever": True},
            }
            plan = PLAN_DATA.get(q.data)
            if not plan:
                await q.answer("❌ Plan nahi mila!", show_alert=True)
                return

            # Save selected plan in user_data for payment flow
            context.user_data['pending_plan'] = q.data
            context.user_data['pending_plan_data'] = plan

            if plan.get("is_forever"):
                detail_text = (
                    "╔══════════════════════════════════╗\n"
                    "║  ♾️  *FOREVER UNLIMITED — ₹1,999*  ♾️  ║\n"
                    "╚══════════════════════════════════╝\n\n"
                    "🎯 *Plan Details:*\n"
                    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                    "✦ *Access:* Lifetime Unlimited\n"
                    "✦ *Validity:* Forever (500 Years)\n"
                    "✦ *Price:* `₹1,999` *(One-Time Only)*\n"
                    "✦ *Searches:* Unlimited — All Features\n"
                    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
                    "👇 *Payment karo — QR scan karke:*\n"
                    "🏦 *Bank Name:* `DHARMENDRA KUMAR`\n"
                    "📲 *UPI ID:* `vaibhavi.mishra@fam`\n\n"
                    "⚠️ *Exactly ₹1,999 bhejo!*\n"
                    "📸 *Payment ke baad screenshot bhejo.*"
                )
            else:
                detail_text = (
                    f"📦 *Plan Details*\n"
                    f"━━━━━━━━━━━━━━━━━━━━\n"
                    f"💳 *Credits:* `{plan['credits']}`\n"
                    f"💰 *Price:* `{plan['price']}`\n"
                    f"━━━━━━━━━━━━━━━━━━━━\n\n"
                    f"👇 *Payment karo — QR scan karke:*\n"
                    f"🏦 *Bank Name:* `DHARMENDRA KUMAR`\n"
                    f"📲 *UPI ID:* `vaibhavi.mishra@fam`\n\n"
                    f"⚠️ *Exact amount {plan['price']} hi bhejo!*"
                )

            kb = InlineKeyboardMarkup([
                [InlineKeyboardButton("✅ I Have Paid", callback_data=f"PAID_{q.data}")],
                [InlineKeyboardButton("🧑‍💻 Admin Se Contact Karo", url=DEV_CHAT)],
                [InlineKeyboardButton("🔙 Back to Plans", callback_data="BUY")],
            ])

            # Send QR image with details as caption
            try:
                await q.message.delete()
            except Exception:
                pass
            try:
                connector = aiohttp.TCPConnector(ssl=False)
                async with aiohttp.ClientSession(connector=connector) as session:
                    async with session.get(
                        QR_IMAGE_URL,
                        timeout=aiohttp.ClientTimeout(total=20),
                        allow_redirects=True,
                        headers={"User-Agent": "Mozilla/5.0"}
                    ) as resp:
                        img_bytes = await resp.read()
                if len(img_bytes) < 1000:
                    raise ValueError("QR image too small — probably not a real image")
                await context.bot.send_photo(
                    chat_id=q.from_user.id,
                    photo=img_bytes,
                    caption=detail_text,
                    parse_mode="Markdown",
                    reply_markup=kb
                )
            except Exception as qr_err:
                logging.warning(f"QR fetch failed: {qr_err}")
                # Fallback: text only agar image fetch fail ho
                await context.bot.send_message(
                    chat_id=q.from_user.id,
                    text=detail_text,
                    parse_mode="Markdown",
                    reply_markup=kb
                )

        # ── I HAVE PAID → Directly ask for Screenshot (UTR skipped) ──
        elif q.data.startswith("PAID_") and not q.data.startswith("PAID_BOT_"):
            plan_key  = q.data[5:]   # e.g. "PLAN_999"
            plan_data = context.user_data.get('pending_plan_data', {})
            if not plan_data:
                await q.answer("❌ Plan data nahi mila. Dobara BUY tap karo.", show_alert=True)
                return
            context.user_data['wait_for']    = "PAYMENT_SCREENSHOT"
            context.user_data['payment_plan'] = plan_key
            txt = (
                "📸 *Payment Screenshot Bhejo*\n\n"
                "Payment complete karne ke baad screenshot yahan bhejo.\n\n"
                "_(Sirf image/photo accept hoga)_"
            )
            try:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await context.bot.send_message(chat_id=q.from_user.id, text=txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── PAID_BOT_CREATE — ask for screenshot for bot payment ──
        elif q.data == "PAID_BOT_CREATE":
            pending_token = context.user_data.get('pending_bot_token')
            if not pending_token:
                await q.answer("❌ Token nahi mila. Dobara CREATE YOUR OWN BOT tap karo.", show_alert=True)
                return
            context.user_data['wait_for'] = "BOT_PAYMENT_SCREENSHOT"
            txt = (
                "📸 *Payment Screenshot Bhejo*\n\n"
                "₹250 payment complete karne ke baad screenshot yahan bhejo.\n\n"
                "_(Sirf image/photo accept hoga)_"
            )
            try:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await context.bot.send_message(chat_id=q.from_user.id, text=txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── ADMIN: APPROVE BOT CREATION ──
        elif q.data.startswith("APPROV_BOT_"):
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin approve kar sakta hai.", show_alert=True)
                return
            target_uid = q.data[len("APPROV_BOT_"):]
            pending    = cols["pending_bots"].find_one({"user_id": target_uid})
            if not pending:
                await q.answer("❌ Pending bot request nahi mila!", show_alert=True)
                return
            bot_token    = pending.get("bot_token")
            bot_username = pending.get("bot_username", "unknown")
            success, msg_txt = await launch_user_bot(
                bot_token, int(target_uid),
                parent_dev_chat=DEV_CHAT,
                parent_token=cfg["BOT_TOKEN"],
                custom_admin_id=ADMIN_ID
            )
            cols["pending_bots"].delete_one({"user_id": target_uid})
            if success:
                try:
                    await context.bot.send_message(
                        chat_id=int(target_uid),
                        text=(
                            f"🎉 *Payment Approved! Bot Live Ho Gaya!*\n\n"
                            f"✅ *Bot:* @{bot_username}\n"
                            f"🚀 *Status:* Running\n\n"
                            f"Ab apne users ko @{bot_username} pe bhejo!\n"
                            f"Enjoy using AVI OSINT! 🔍"
                        ),
                        parse_mode="Markdown",
                        reply_markup=back_to_main_keyboard()
                    )
                except Exception as e:
                    logging.warning(f"Bot approval notify error: {e}")
                try:
                    await q.message.edit_caption(
                        caption=q.message.caption + f"\n\n✅ *APPROVED & LAUNCHED* — @{bot_username}",
                        parse_mode="Markdown",
                        reply_markup=None
                    )
                except Exception:
                    pass
                await q.answer(f"✅ @{bot_username} live ho gaya!", show_alert=True)
            else:
                await q.answer(f"❌ Launch failed: {msg_txt[:100]}", show_alert=True)

        # ── ADMIN: REJECT BOT CREATION ──
        elif q.data.startswith("REJECT_BOT_"):
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin reject kar sakta hai.", show_alert=True)
                return
            target_uid = q.data[len("REJECT_BOT_"):]
            cols["pending_bots"].delete_one({"user_id": target_uid})
            try:
                await context.bot.send_message(
                    chat_id=int(target_uid),
                    text=(
                        "❌ *Bot Creation Request Rejected!*\n\n"
                        "Tumhari payment verify nahi ho payi.\n\n"
                        "🔁 *Dobara try karo ya admin se contact karo.*"
                    ),
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
            except Exception:
                pass
            try:
                await q.message.edit_caption(
                    caption=q.message.caption + "\n\n❌ *REJECTED*",
                    parse_mode="Markdown",
                    reply_markup=None
                )
            except Exception:
                pass
            await q.answer("❌ Bot creation reject kar diya.", show_alert=True)

        # ── REFER ──
        elif q.data == "REFER":
            bot_un   = (await context.bot.get_me()).username
            ref_link = f"https://t.me/{bot_un}?start={u_id}"
            refer_text = (
                f"🔗 *Refer Link:*\n`{ref_link}`\n\n"
                f"📊 *Total Referrals:* {user_doc.get('refer_count', 0) if user_doc else 0}\n\n"
                f"💰 Earn *1 Credit* per refer!"
            )
            try:
                await q.message.edit_text(refer_text, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
            except Exception:
                await q.message.reply_text(refer_text, parse_mode="Markdown", reply_markup=back_to_main_keyboard())

        # ── CHECK-IN ──
        elif q.data == "CHECKIN":
            now      = datetime.utcnow()
            existing = cols["checkin"].find_one({"user_id": u_id})
            if existing:
                last_checkin = existing.get("last_checkin")
                if last_checkin and (now - last_checkin) < timedelta(hours=24):
                    txt = "❌ *CHECK-IN FAILED*\n\nSirf har 24 ghante mein ek baar check-in ho sakta hai."
                    try:
                        await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                    except Exception:
                        await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                    return
            cols["checkin"].update_one({"user_id": u_id}, {"$set": {"last_checkin": now}}, upsert=True)
            cols["users"].update_one({"user_id": u_id}, {"$inc": {"safe_credits": 0.1}})
            updated = cols["users"].find_one({"user_id": u_id})
            new_bal = float(updated.get("credits", 0.0)) + float(updated.get("safe_credits", 0.0)) if updated else 0.0
            txt = (
                f"✅ *CHECK-IN SUCCESSFUL!*\n\n"
                f"🎁 *+0.1 Credit Added*\n"
                f"💳 *New Balance:* `{new_bal}`"
            )
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=back_to_main_keyboard())

        # ── WATCH & EARN ──
        elif q.data == "WATCH_EARN":
            txt = "🔜 *COMING SOON!*\n\nWatch & Earn feature jaldi aa raha hai. Stay tuned!"
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=back_to_main_keyboard())

        # ── COMING SOON (credit-aware) ──
        # UPI, Rations, Fampay, Instagram OSINT, Live Location → all SOON_10
        # < 10 credits: show buy; >= 10 credits: show coming soon
        elif q.data == "SOON_10":
            u_credits = get_effective_credits(cols, user_doc)
            if u_credits < 10:
                txt = (
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:* `{u_credits} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*"
                )
                kb = InlineKeyboardMarkup([
                    [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                    [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                ])
            else:
                txt = "🔜 *COMING SOON!*\n\nYe feature jaldi available hoga. Stay tuned!"
                kb  = back_to_main_keyboard()
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=kb)

        # ── UPI INFO ──
        elif q.data == "UPI_INFO":
            context.user_data['wait_for'] = "UPI_INFO"
            txt = (
                "💳 *UPI INFO LOOKUP*\n\n"
                "UPI ID ya number enter karo:\n\n"
                "Example: `someone@upi` ya `9876543210`\n\n"
                "💳 *Cost: 2 Credits*"
            )
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── VEH_TO_NUM ──
        elif q.data == "VEH_TO_NUM":
            context.user_data['wait_for'] = "VEH_TO_NUM"
            txt = (
                "🚘 *VEHICLE TO NUMBER*\n\n"
                "Vehicle/number plate enter karo:\n\n"
                "Example: `UP32QP0001`\n\n"
                "💳 *Cost: 10 Credits*"
            )
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── TG USERNAME ──
        elif q.data == "TG_USERNAME":
            context.user_data['wait_for'] = "TG_USERNAME"
            txt = (
                "📞 *ENTER TG USERNAME*\n\n"
                "@ ke saath ya bina — dono chalega\n\n"
                "Example: `@username123` ya `username123`\n\n"
                "💳 *Cost: 10 Credits*"
            )
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── CREATE YOUR OWN BOT ──
        elif q.data == "BOT_SERVICE":
            is_master = (q.from_user.id == ADMIN_ID)
            if is_master:
                context.user_data['wait_for'] = "CREATE_BOT_MASTER_TOKEN"
                txt = (
                    "🤖 *CREATE MIRROR BOT (Master Admin)*\n\n"
                    "Bot ka *API Token* bhejo.\n\n"
                    "📌 Format: `1234567890:AAHxxxxxx`\n\n"
                    "_(Sub-admin baad mein /setadmin se set karo)_"
                )
            else:
                # Normal user flow: sirf token
                context.user_data['wait_for'] = "CREATE_BOT"
                txt = (
                    "🤖 *CREATE YOUR OWN BOT*\n\n"
                    "Apne bot ka API Token bhejo.\n\n"
                    "*Token kaise milega?*\n"
                    "1️⃣ @BotFather pe jao\n"
                    "2️⃣ /newbot command do\n"
                    "3️⃣ Naam aur username set karo\n"
                    "4️⃣ Token copy karke yahan bhejo\n\n"
                    "📌 Token format: `1234567890:AAHxxxxxx`"
                )
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── RATIONS INFO ──
        elif q.data == "RATIONS_INFO":
            u_credits = get_effective_credits(cols, user_doc)
            if u_credits < 2:
                txt = (
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:* `{u_credits} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*"
                )
                kb = InlineKeyboardMarkup([
                    [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                    [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                ])
            else:
                context.user_data['wait_for'] = "RATIONS_INFO"
                txt = (
                    "🧺 *RATION INFO*\n\n"
                    "Aadhaar number enter karo:\n\n"
                    "📌 *Format:* `XXXXXXXXXXXX` _(12 digit)_\n"
                    "Example: `123456789012`\n\n"
                    "💳 *Cost: 2 Credits*"
                )
                kb = cancel_keyboard()
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=kb)

        # ── PAK INFO ──
        elif q.data == "PAK_INFO":
            u_credits = get_effective_credits(cols, user_doc)
            if u_credits < 2:
                txt = (
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:* `{u_credits} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*"
                )
                kb = InlineKeyboardMarkup([
                    [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                    [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                ])
            else:
                context.user_data['wait_for'] = "PAK_INFO"
                txt = (
                    "🇵🇰 *PAK INFO*\n\n"
                    "Pakistan ka mobile number enter karo.\n\n"
                    "📌 *Format:* `3XXXXXXXXX` _(10 digits, 03 ya +92 mat likho)_\n"
                    "Example: `3429533570`\n\n"
                    "💳 *Cost: 2 Credits*"
                )
                kb = cancel_keyboard()
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=kb)

        # ── INSTAGRAM OSINT ──
        elif q.data == "INSTAGRAM_OSINT":
            u_credits = get_effective_credits(cols, user_doc)
            if u_credits < 2:
                txt = (
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:* `{u_credits} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*"
                )
                kb = InlineKeyboardMarkup([
                    [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                    [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                ])
            else:
                context.user_data['wait_for'] = "INSTAGRAM"
                txt = (
                    "📲 *INSTAGRAM OSINT*\n\n"
                    "Instagram username enter karo:\n\n"
                    "Example: `username123` ya `@username123`\n\n"
                    "💳 *Cost: 2 Credits*"
                )
                kb = cancel_keyboard()
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=kb)

        # ── LIVE LOCATION ──
        elif q.data == "LIVE_LOCATION":
            u_credits = get_effective_credits(cols, user_doc)
            if u_credits < 2:
                txt = (
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:* `{u_credits} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*"
                )
                kb = InlineKeyboardMarkup([
                    [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                    [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                ])
            else:
                txt = (
                    "📍 *LIVE LOCATION*\n\n"
                    "⏳ Ye feature abhi development mein hai.\n"
                    "Jaldi available hoga — Stay tuned!"
                )
                kb = back_to_main_keyboard()
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=kb)

        # ── FREE FIRE LOOKUP ──
        elif q.data == "FREE_FIRE":
            u_credits = get_effective_credits(cols, user_doc)
            if u_credits < 2:
                txt = (
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:* `{u_credits} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*"
                )
                kb = InlineKeyboardMarkup([
                    [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                    [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                ])
            else:
                context.user_data['wait_for'] = "FREE_FIRE"
                txt = (
                    "🔥 *FREE FIRE LOOKUP*\n\n"
                    "Free Fire Player UID enter karo:\n\n"
                    "Example: `2240460996`\n\n"
                    "💳 *Cost: 2 Credits*"
                )
                kb = cancel_keyboard()
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=kb)

        # ── PROTECT SERVICE ──
        elif q.data == "PROTECT_SERVICE":
            txt = "🛡️ *PROTECT YOUR NUMBER*\n\nIs service ke liye admin se contact karo."
            kb  = InlineKeyboardMarkup([
                [InlineKeyboardButton("ADMIN 🧑‍💻", url=DEV_CHAT)],
                [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
            ])
            try:
                await q.message.edit_text(txt, parse_mode="Markdown", reply_markup=kb)
            except Exception:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=kb)

        # ── SPECIAL OFFER 999 ──
        elif q.data == "SPECIAL_OFFER_999":
            # Check if offer is still valid
            fresh_doc    = cols["users"].find_one({"user_id": u_id})
            offer_expiry = fresh_doc.get("special_offer_expires") if fresh_doc else None
            now_utc      = datetime.utcnow()

            if offer_expiry and offer_expiry < now_utc:
                # Offer expired
                try:
                    await q.message.edit_text(
                        "⏰ *Offer Expired!*\n\n"
                        "Sorry, ye special offer ab valid nahi raha.\n\n"
                        "💡 _Regular plans ke liye BUY CREDIT tap karo._",
                        parse_mode="Markdown",
                        reply_markup=InlineKeyboardMarkup([
                            [InlineKeyboardButton("💰 View Plans", callback_data="BUY")],
                            [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                        ])
                    )
                except Exception:
                    await q.answer("⏰ Offer Expired!", show_alert=True)
                return

            # Show offer payment details with QR
            detail_text = (
                "╔══════════════════════════════════╗\n"
                "║  ♾️  *LIFETIME UNLIMITED — ₹999*  ♾️  ║\n"
                "╚══════════════════════════════════╝\n\n"
                "🎯 *Plan Details:*\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                "✦ *Access:* Lifetime Unlimited\n"
                "✦ *Validity:* Forever (500 Years)\n"
                "✦ *Price:* `₹999` *(One-Time Only)*\n"
                "✦ *Searches:* Unlimited — All Features\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
                "👇 *Payment karo — QR scan karke:*\n"
                "🏦 *Bank Name:* `DHARMENDRA KUMAR`\n"
                "📲 *UPI ID:* `vaibhavi.mishra@fam`\n\n"
                "⚠️ *Exactly ₹999 bhejo!*\n"
                "📸 *Payment ke baad screenshot bhejo.*"
            )
            context.user_data['pending_plan']      = "SPECIAL_OFFER_999"
            context.user_data['pending_plan_data'] = {
                "credits":  "Lifetime Unlimited",
                "price":    "₹999",
                "validity": "Lifetime (500 Years)",
                "amount":   999,
                "is_unlim": True,
                "is_special_offer": True,
            }
            kb = InlineKeyboardMarkup([
                [InlineKeyboardButton("✅ I Have Paid — Send Screenshot", callback_data="PAID_SPECIAL_OFFER")],
                [InlineKeyboardButton("🧑‍💻 Contact Admin", url=DEV_CHAT)],
                [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
            ])
            try:
                await q.message.delete()
            except Exception:
                pass
            try:
                async with aiohttp.ClientSession() as session:
                    async with session.get(QR_IMAGE_URL, timeout=aiohttp.ClientTimeout(total=15)) as resp:
                        img_bytes = await resp.read()
                await context.bot.send_photo(
                    chat_id=q.from_user.id,
                    photo=img_bytes,
                    caption=detail_text,
                    parse_mode="Markdown",
                    reply_markup=kb
                )
            except Exception:
                await context.bot.send_message(
                    chat_id=q.from_user.id,
                    text=detail_text,
                    parse_mode="Markdown",
                    reply_markup=kb
                )

        # ── SPECIAL OFFER PAID — Ask screenshot ──
        elif q.data == "PAID_SPECIAL_OFFER":
            plan_data = context.user_data.get('pending_plan_data', {})
            if not plan_data:
                await q.answer("❌ Offer data nahi mila. Dobara GRAB IT tap karo.", show_alert=True)
                return
            context.user_data['wait_for']     = "PAYMENT_SCREENSHOT"
            context.user_data['payment_plan'] = "SPECIAL_OFFER_999"
            txt = (
                "📸 *Payment Screenshot Bhejo*\n\n"
                "₹999 payment complete karne ke baad screenshot yahan bhejo.\n\n"
                "_(Sirf image/photo accept hoga)_"
            )
            try:
                await q.message.reply_text(txt, parse_mode="Markdown", reply_markup=cancel_keyboard())
            except Exception:
                await context.bot.send_message(chat_id=q.from_user.id, text=txt, parse_mode="Markdown", reply_markup=cancel_keyboard())

        # ── MIRROR BOT MANAGEMENT BUTTONS (master admin only) ──
        elif q.data.startswith("MBOT_"):
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin kar sakta hai.", show_alert=True)
                return
            parts     = q.data.split("_", 2)
            action    = parts[1]   # INFO, START, STOP, DEL
            tok_prefix = parts[2] if len(parts) > 2 else ""
            mirror_tokens = get_mirror_bots_for_parent(cfg["BOT_TOKEN"])
            matched = [t for t in mirror_tokens if t.startswith(tok_prefix)]

            if not matched and action != "LIST":
                await q.answer("❌ Bot nahi mila. Shayad pehle se stop ho gaya.", show_alert=True)
                await show_mirror_bot_list(q.message, context, edit=True)
                return

            tok   = matched[0] if matched else ""
            info  = user_bots.get(tok) or stopped_bots.get(tok) or {}
            uname = info.get("username", "?") if isinstance(info, dict) else "?"

            if action == "INFO":
                m_db_name = stable_db_name(tok)
                m_cols    = get_db(m_db_name)
                total_u   = m_cols["users"].count_documents({})
                owner_id  = info.get("owner_id", "?") if isinstance(info, dict) else "?"
                txt = (
                    f"🤖 *Bot Info*\n"
                    f"━━━━━━━━━━━━━━━━━━━━\n"
                    f"📛 *Username:* @{uname}\n"
                    f"👤 *Owner ID:* `{owner_id}`\n"
                    f"👥 *Total Users:* `{total_u}`\n"
                    f"🔑 *Token (partial):* `{tok[:25]}...`"
                )
                try:
                    await q.message.edit_text(txt, parse_mode="Markdown",
                        reply_markup=InlineKeyboardMarkup([
                            [InlineKeyboardButton("🔙 Back to Mirror Bots", callback_data="MIRROR_BOT_LIST")]
                        ]))
                except Exception:
                    await q.answer(txt[:200], show_alert=True)

            elif action == "STOP":
                if tok not in user_bots:
                    await q.answer(f"⚠️ @{uname} pehle se stop hai.", show_alert=True)
                else:
                    # Save metadata to stopped_bots before removing from user_bots
                    info_data = user_bots.get(tok, {})
                    stopped_bots[tok] = {
                        "owner_id":        info_data.get("owner_id", ADMIN_ID),
                        "username":        info_data.get("username", uname),
                        "parent_token":    info_data.get("parent_token", cfg["BOT_TOKEN"]),
                        "admin_id":        info_data.get("admin_id", ADMIN_ID),
                        "parent_dev_chat": info_data.get("parent_dev_chat", DEV_CHAT),
                    }
                    persist_stopped_bot(tok, stopped_bots[tok])
                    remove_persisted_running_bot(tok)   # ← remove from running registry
                    user_bots.pop(tok, None)
                    await q.answer(f"⏹ @{uname} stop kar diya! (Remove nahi hua)", show_alert=True)
                await show_mirror_bot_list(q.message, context, edit=True)

            elif action == "START":
                if tok in user_bots:
                    await q.answer(f"✅ @{uname} pehle se chal raha hai!", show_alert=True)
                else:
                    # Get info from stopped_bots or fallback to current info
                    saved_info   = stopped_bots.get(tok, info if isinstance(info, dict) else {})
                    owner_id_v   = saved_info.get("owner_id", ADMIN_ID)
                    custom_admin = saved_info.get("admin_id", ADMIN_ID)
                    p_dev_chat   = saved_info.get("parent_dev_chat", DEV_CHAT)
                    success, msg_txt = await launch_user_bot(
                        tok, owner_id_v,
                        parent_dev_chat=p_dev_chat,
                        parent_token=cfg["BOT_TOKEN"],
                        custom_admin_id=custom_admin
                    )
                    if success:
                        await q.answer(f"▶️ @{uname} start ho gaya!", show_alert=True)
                    else:
                        await q.answer(f"❌ Start failed: {msg_txt[:100]}", show_alert=True)
                await show_mirror_bot_list(q.message, context, edit=True)

            elif action == "DEL":
                # Save metadata before stopping app
                info_data = user_bots.get(tok, {})
                user_bots.pop(tok, None)
                stopped_bots.pop(tok, None)  # Also remove from stopped list
                remove_persisted_running_bot(tok)   # ← remove from running registry
                remove_persisted_stopped_bot(tok)   # ← remove from stopped registry too
                try:
                    m_db_name = stable_db_name(tok)
                    mongo_client.drop_database(m_db_name)
                except Exception:
                    pass
                await q.answer(f"🗑 @{uname} permanently delete ho gaya!", show_alert=True)
                await show_mirror_bot_list(q.message, context, edit=True)

        elif q.data == "MIRROR_BOT_LIST":
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin.", show_alert=True)
                return
            await show_mirror_bot_list(q.message, context, edit=True)

        # ── ADMIN: APPROVE PAYMENT ──
        elif q.data.startswith("APPROV_"):
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin approve kar sakta hai.", show_alert=True)
                return
            # Format: APPROV_<user_id>_<plan_key>
            parts = q.data.split("_", 2)
            if len(parts) < 3:
                await q.answer("❌ Invalid data.", show_alert=True)
                return
            target_uid = parts[1]
            plan_key   = parts[2]  # e.g. PLAN_999

            # ── Duplicate approval check: kya pehle se approve ho chuka hai? ──
            already_doc = cols["users"].find_one({"user_id": f"APPROVED_{target_uid}_{plan_key}"})
            if already_doc:
                approver_name = already_doc.get("approved_by_name", "Dusre Admin")
                await q.answer(f"⚠️ Already approved by {approver_name}!", show_alert=True)
                return

            # Mark as approved with approver info
            approver_uname = q.from_user.username or str(q.from_user.id)
            approver_name  = f"@{approver_uname}" if q.from_user.username else str(q.from_user.id)
            cols["users"].update_one(
                {"user_id": f"APPROVED_{target_uid}_{plan_key}"},
                {"$set": {
                    "approved_by": q.from_user.id,
                    "approved_by_name": approver_name,
                    "approved_at": datetime.utcnow(),
                }},
                upsert=True
            )

            PLAN_DB_DATA = {
                "PLAN_249":          {"credits": 50.0,  "days": 365,    "is_unlim": False},
                "PLAN_500":          {"credits": 250.0, "days": 365,    "is_unlim": False},
                "PLAN_999":          {"credits": 500.0, "days": 365,    "is_unlim": False},
                "PLAN_UNLIM_7":      {"credits": 0,     "days": 7,      "is_unlim": True},
                "PLAN_UNLIM_30":     {"credits": 0,     "days": 30,     "is_unlim": True},
                "PLAN_FOREVER":      {"credits": 0,     "days": 182500, "is_unlim": True},
                # Special Offer — 500 Years Lifetime
                "SPECIAL_OFFER_999": {"credits": 0,     "days": 182500, "is_unlim": True},
            }
            db_plan = PLAN_DB_DATA.get(plan_key)
            if not db_plan:
                await q.answer("❌ Plan data nahi mila!", show_alert=True)
                return

            base_expiry = datetime.utcnow() + timedelta(days=db_plan["days"])

            # For credit-based plans: keep max of existing future expiry vs new expiry
            if not db_plan["is_unlim"]:
                _existing = cols["users"].find_one({"user_id": target_uid})
                _ex_exp   = _existing.get("credits_expires_at") if _existing else None
                _now      = datetime.utcnow()
                if _ex_exp and _ex_exp > _now:
                    expires_at = max(_ex_exp, base_expiry)
                else:
                    expires_at = base_expiry
            else:
                expires_at = base_expiry
            is_special_lifetime = (plan_key in ("SPECIAL_OFFER_999", "PLAN_FOREVER"))

            if db_plan["is_unlim"]:
                cols["users"].update_one(
                    {"user_id": target_uid},
                    {"$set": {
                        "unlimited_plan": True,
                        "unlimited_expires_at": expires_at,
                    }},
                    upsert=True
                )
                credit_label = "♾️ Lifetime Unlimited (500 Years)" if is_special_lifetime else "♾️ Unlimited"
            else:
                cols["users"].update_one(
                    {"user_id": target_uid},
                    {
                        "$inc": {"credits": db_plan["credits"]},
                        "$set": {"credits_expires_at": expires_at}
                    },
                    upsert=True
                )
                credit_label = f"{int(db_plan['credits'])} Credits"

            # Notify user
            try:
                if is_special_lifetime:
                    approval_text = (
                        "╔══════════════════════════════════╗\n"
                        "║  🏆  *LIFETIME ACCESS ACTIVATED!*  🏆  ║\n"
                        "╚══════════════════════════════════╝\n\n"
                        "🎊 *Congratulations! Welcome to the Elite Club!*\n\n"
                        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                        f"♾️ *Plan:* `Lifetime Unlimited`\n"
                        f"🕐 *Duration:* `500 Years`\n"
                        f"📅 *Valid Till:* `{expires_at.strftime('%d %b %Y')}`\n"
                        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
                        "✅ You now have *UNLIMITED access* to every feature — forever!\n"
                        "🔍 Search anything, anytime, without any restrictions.\n\n"
                        "🚀 *Welcome aboard — Enjoy AVI OSINT for life!* 🔍"
                    )
                else:
                    approval_text = (
                        f"🎉 *Payment Approved!*\n\n"
                        f"✅ *{credit_label} added to your account!*\n\n"
                        f"Enjoy using AVI OSINT! 🔍"
                    )
                await context.bot.send_message(
                    chat_id=int(target_uid),
                    text=approval_text,
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
            except Exception as e:
                logging.warning(f"User notify error: {e}")

            # ── 🔥 Special Offer Message (24hr window) ──
            if not is_special_lifetime:
                offer_expires_at = datetime.utcnow() + timedelta(hours=24)
                # Save offer expiry in DB for this user
                cols["users"].update_one(
                    {"user_id": str(target_uid)},
                    {"$set": {"special_offer_expires": offer_expires_at}},
                    upsert=True
                )
                offer_text = (
                    "╔══════════════════════════════════╗\n"
                    "║  🔓  *EXCLUSIVE OFFER UNLOCKED!*  🔓  ║\n"
                    "╚══════════════════════════════════╝\n\n"
                    "🚀 *Congratulations on your purchase!*\n"
                    "As a valued customer, we have unlocked a *special one-time deal* just for you:\n\n"
                    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                    "♾️  *LIFETIME UNLIMITED ACCESS*\n"
                    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                    "✦ Unlimited searches — *forever*\n"
                    "✦ All features — *no restrictions*\n"
                    "✦ No monthly renewals — *ever again*\n"
                    "✦ Priority access to new features\n\n"
                    "💰 *Special Price:* `₹999 only`\n"
                    f"⏳ *Offer Expires In:* `24 Hours`\n"
                    f"🕐 *Valid Till:* `{offer_expires_at.strftime('%d %b %Y, %H:%M')} UTC`\n\n"
                    "⚠️ *This offer will NOT appear again after expiry!*\n"
                    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                )
                try:
                    await context.bot.send_message(
                        chat_id=int(target_uid),
                        text=offer_text,
                        parse_mode="Markdown",
                        reply_markup=InlineKeyboardMarkup([
                            [InlineKeyboardButton("🔥 GRAB IT NOW!", callback_data="SPECIAL_OFFER_999")],
                            [InlineKeyboardButton("❌ Maybe Later", callback_data="MAIN_MENU")],
                        ])
                    )
                except Exception as e:
                    logging.warning(f"Offer message error: {e}")

            # ── Dusre admin ko notify karo ──
            for _other_adm in get_all_admin_ids():
                if _other_adm != q.from_user.id:
                    try:
                        await context.bot.send_message(
                            chat_id=_other_adm,
                            text=(
                                f"ℹ️ *Payment Already Approved!*\n\n"
                                f"✅ {approver_name} ne already payment approve kar diya hai!\n\n"
                                f"👤 *User ID:* `{target_uid}`\n"
                                f"💳 *Plan:* `{plan_key}`\n"
                                f"🎁 *Credits:* `{credit_label}`"
                            ),
                            parse_mode="Markdown"
                        )
                    except Exception:
                        pass

            # Edit admin message — buttons hide karo
            try:
                await q.message.edit_caption(
                    caption=q.message.caption + f"\n\n✅ *APPROVED by {approver_name}* — {credit_label} added!",
                    parse_mode="Markdown",
                    reply_markup=None  # Buttons hide
                )
            except Exception:
                pass
            await q.answer(f"✅ {credit_label} user ko de diya!", show_alert=True)

        # ── ADMIN: REJECT PAYMENT ──
        elif q.data.startswith("REJECT_"):
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin reject kar sakta hai.", show_alert=True)
                return
            target_uid = q.data.split("_", 1)[1]
            try:
                await context.bot.send_message(
                    chat_id=int(target_uid),
                    text=(
                        "❌ *Payment Rejected!*\n\n"
                        "Tumhari payment verify nahi ho payi.\n\n"
                        "🔁 *Dobara try karo ya admin se contact karo.*"
                    ),
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
            except Exception:
                pass
            try:
                await q.message.edit_caption(
                    caption=q.message.caption + "\n\n❌ *REJECTED*",
                    parse_mode="Markdown",
                    reply_markup=None  # Buttons hide
                )
            except Exception:
                pass
            await q.answer("❌ Payment reject kar diya.", show_alert=True)

        # ── SEARCH BUTTONS ──
        elif q.data in ["NUMBER", "AADHAR", "FAMILY", "VEHICLE", "CAR", "PAN", "EMAIL_LOOKUP"]:
            context.user_data['wait_for'] = q.data
            prompts = {
                "NUMBER":       "📱 *ENTER 10 DIGIT MOBILE NUMBER*\n\nExample: `9876543210`\n\n💳 *Cost: 2 Credits*",
                "AADHAR":       "🆔 *ENTER 12 DIGIT AADHAR NUMBER*\n\nExample: `098765432112`\n\n💳 *Cost: 2 Credits*",
                "FAMILY":       "👨‍👩‍👧 *ENTER 12 DIGIT AADHAR NUMBER*\n\nExample: `098765432112`\n\n💳 *Cost: 2 Credits*",
                "VEHICLE":      "🚗 *ENTER VEHICLE NUMBER*\n\nExample: `BR77AJ1234`\n\n💳 *Cost: 2 Credits*",
                "CAR":          "🚘 *ENTER VEHICLE NUMBER*\n\nExample: `BR77AJ1234`\n\n💳 *Cost: 2 Credits*",
                "PAN":          "💳 *ENTER PAN NUMBER*\n\nExample: `ABCDE1234F`\n\n💳 *Cost: 2 Credits*",
                "EMAIL_LOOKUP": "📧 *ENTER EMAIL ADDRESS*\n\nExample: `user123@email.com`\n\n💳 *Cost: 2 Credits*",
            }
            try:
                await q.message.edit_text(
                    prompts.get(q.data, "Enter details:"), parse_mode="Markdown", reply_markup=cancel_keyboard()
                )
            except Exception:
                await q.message.reply_text(
                    prompts.get(q.data, "Enter details:"), parse_mode="Markdown", reply_markup=cancel_keyboard()
                )

        # ── SETADMIN — mirror bot select karke sub-admin add ──
        elif q.data.startswith("SADMIN_SET_"):
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin.", show_alert=True)
                return
            # Format: SADMIN_SET_<tok_prefix>_<target_id>
            rest       = q.data[len("SADMIN_SET_"):]
            tok_prefix = rest[:15]
            target_id  = int(rest[16:])   # after tok_prefix(15) + underscore(1)

            # Token dhundo
            mirror_tokens = get_mirror_bots_for_parent(cfg["BOT_TOKEN"])
            matched = [t for t in mirror_tokens if t.startswith(tok_prefix)]
            if not matched:
                await q.answer("❌ Bot nahi mila.", show_alert=True)
                return
            tok    = matched[0]
            info   = user_bots.get(tok) or stopped_bots.get(tok) or {}
            uname  = info.get("username", "unknown") if isinstance(info, dict) else "unknown"

            # Us mirror bot ke DB mein sub-admin add karo
            m_cols = get_db(stable_db_name(tok))
            m_cols["users"].update_one(
                {"user_id": "SUB_ADMINS"},
                {"$addToSet": {"ids": target_id}},
                upsert=True
            )
            try:
                await q.message.edit_text(
                    f"✅ *Sub-Admin Added!*\n\n"
                    f"👤 User: `{target_id}`\n"
                    f"🤖 Mirror Bot: @{uname}\n\n"
                    f"_Ye user us bot mein sirf /add use kar sakta hai._",
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
            except Exception:
                await q.answer(f"✅ {target_id} added to @{uname}", show_alert=True)

        # ── REMOVEADMIN — mirror bot select karke sub-admin remove ──
        elif q.data.startswith("SADMIN_REM_"):
            if not is_any_admin(q.from_user.id):
                await q.answer("❌ Sirf admin.", show_alert=True)
                return
            rest       = q.data[len("SADMIN_REM_"):]
            tok_prefix = rest[:15]
            target_id  = int(rest[16:])

            mirror_tokens = get_mirror_bots_for_parent(cfg["BOT_TOKEN"])
            matched = [t for t in mirror_tokens if t.startswith(tok_prefix)]
            if not matched:
                await q.answer("❌ Bot nahi mila.", show_alert=True)
                return
            tok    = matched[0]
            info   = user_bots.get(tok) or stopped_bots.get(tok) or {}
            uname  = info.get("username", "unknown") if isinstance(info, dict) else "unknown"

            m_cols = get_db(stable_db_name(tok))
            m_cols["users"].update_one(
                {"user_id": "SUB_ADMINS"},
                {"$pull": {"ids": target_id}}
            )
            try:
                await q.message.edit_text(
                    f"✅ *Sub-Admin Removed!*\n\n"
                    f"👤 User: `{target_id}`\n"
                    f"🤖 Mirror Bot: @{uname}",
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
            except Exception:
                await q.answer(f"✅ {target_id} removed from @{uname}", show_alert=True)

    # ========================================================
    # =================== PHOTO HANDLER ======================
    # ========================================================
    async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
        """Handle screenshot upload during payment flow."""
        mode = context.user_data.get('wait_for')
        if mode not in ("PAYMENT_SCREENSHOT", "BOT_PAYMENT_SCREENSHOT"):
            return

        user    = update.effective_user
        user_id = str(user.id)
        uname   = user.username or ""
        uname_str = f"@{uname}" if uname else "—"
        tg_name = user.first_name or "—"

        photo_file = update.message.photo[-1].file_id

        # ── BOT CREATION PAYMENT ──
        if mode == "BOT_PAYMENT_SCREENSHOT":
            context.user_data.pop('wait_for', None)
            bot_token    = context.user_data.pop('pending_bot_token',    None)
            bot_username = context.user_data.pop('pending_bot_username', "unknown")
            if not bot_token:
                await update.message.reply_text(
                    "❌ Bot token nahi mila. Dobara CREATE YOUR OWN BOT tap karo.",
                    reply_markup=back_to_main_keyboard()
                )
                return
            # Save to DB for admin to approve later
            cols["pending_bots"].update_one(
                {"user_id": user_id},
                {"$set": {
                    "user_id":      user_id,
                    "bot_token":    bot_token,
                    "bot_username": bot_username,
                    "submitted_at": datetime.utcnow(),
                }},
                upsert=True
            )
            admin_caption = (
                f"🤖 *NEW BOT CREATION REQUEST*\n"
                f"━━━━━━━━━━━━━━━━━━━━\n"
                f"👤 *Name:* {tg_name}\n"
                f"📝 *Username:* {uname_str}\n"
                f"🆔 *User ID:* `{user_id}`\n"
                f"━━━━━━━━━━━━━━━━━━━━\n"
                f"🤖 *Bot:* @{bot_username}\n"
                f"🔑 *Token (partial):* `{bot_token[:20]}...`\n"
                f"💰 *Amount:* `₹250`\n"
                f"━━━━━━━━━━━━━━━━━━━━"
            )
            admin_kb = InlineKeyboardMarkup([
                [
                    InlineKeyboardButton("✅ Approve & Launch", callback_data=f"APPROV_BOT_{user_id}"),
                    InlineKeyboardButton("❌ Reject",           callback_data=f"REJECT_BOT_{user_id}"),
                ]
            ])
            for _adm_id in get_all_admin_ids():
                try:
                    await context.bot.send_photo(
                        chat_id=_adm_id,
                        photo=photo_file,
                        caption=admin_caption,
                        parse_mode="Markdown",
                        reply_markup=admin_kb
                    )
                except Exception as e:
                    logging.error(f"Admin bot-creation forward error (admin {_adm_id}): {e}")
            await update.message.reply_text(
                "✅ *Payment Request Submit Ho Gayi!*\n\n"
                f"🤖 *Bot:* @{bot_username}\n"
                "💰 *Amount:* ₹250\n\n"
                "⏳ *Admin verify karega. Approval ke baad tumhara bot live ho jayega!*\n\n"
                "🔔 Approval hone par automatic notification milega!",
                parse_mode="Markdown",
                reply_markup=InlineKeyboardMarkup([
                    [InlineKeyboardButton("🧑‍💻 Admin Se Contact Karo", url=DEV_CHAT)],
                    [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                ])
            )
            return

        plan_key  = context.user_data.get('payment_plan', '—')
        plan_data = context.user_data.get('pending_plan_data', {})

        credits_label  = plan_data.get('credits', '—')
        price_label    = plan_data.get('price', '—')
        validity_label = plan_data.get('validity', '—')
        amount         = plan_data.get('amount', 0)
        is_unlim       = plan_data.get('is_unlim', False)

        # Clear state
        context.user_data.pop('wait_for', None)
        context.user_data.pop('payment_utr', None)
        context.user_data.pop('payment_plan', None)
        context.user_data.pop('pending_plan', None)
        context.user_data.pop('pending_plan_data', None)

        # ── Forward screenshot + details to admin ──
        admin_caption = (
            f"💸 *NEW PAYMENT REQUEST*\n"
            f"━━━━━━━━━━━━━━━━━━━━\n"
            f"👤 *Name:* {tg_name}\n"
            f"📝 *Username:* {uname_str}\n"
            f"🆔 *User ID:* `{user_id}`\n"
            f"━━━━━━━━━━━━━━━━━━━━\n"
            f"💳 *Credits:* `{credits_label}`\n"
            f"💰 *Amount:* `{price_label}`\n"
            f"⏰ *Validity:* `{validity_label}`\n"
            f"━━━━━━━━━━━━━━━━━━━━\n"
            f"📋 *Plan Code:* `{plan_key}`"
        )

        # Admin approve/reject keyboard
        admin_kb = InlineKeyboardMarkup([
            [
                InlineKeyboardButton("✅ Approve", callback_data=f"APPROV_{user_id}_{plan_key}"),
                InlineKeyboardButton("❌ Reject",  callback_data=f"REJECT_{user_id}"),
            ]
        ])

        photo_file = update.message.photo[-1].file_id
        for _adm_id in get_all_admin_ids():
            try:
                await context.bot.send_photo(
                    chat_id=_adm_id,
                    photo=photo_file,
                    caption=admin_caption,
                    parse_mode="Markdown",
                    reply_markup=admin_kb
                )
            except Exception as e:
                logging.error(f"Admin forward error (admin {_adm_id}): {e}")

        # ── Notify user: request received ──
        await update.message.reply_text(
            "✅ *Payment Request Submit Ho Gayi!*\n\n"
            "📋 *Details:*\n"
            f"💳 Credits: `{credits_label}`\n"
            f"💰 Amount: `{price_label}`\n\n"
            "⏳ *Admin verify karega. Thodi der mein credits mil jayenge.*\n\n"
            "🔔 Approval hone par automatic notification milega!",
            parse_mode="Markdown",
            reply_markup=InlineKeyboardMarkup([
                [InlineKeyboardButton("🧑‍💻 Admin Se Contact Karo", url=DEV_CHAT)],
                [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
            ])
        )

    # ========================================================
    # =================== MESSAGE HANDLER ====================
    # ========================================================
    async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
        mode = context.user_data.get('wait_for')
        if not mode:
            return

        user_id   = str(update.effective_user.id)
        input_txt = update.message.text.strip()
        user_info = cols["users"].find_one({"user_id": user_id})

        # ── Mirror bot stopped-by-parent-admin check ──
        if not is_any_admin(update.effective_user.id) and cfg["BOT_TOKEN"] in stopped_bots:
            context.user_data.pop('wait_for', None)
            await update.message.reply_text(
                "🔴 *BOT STOPPED BY ADMIN*\n\n"
                "This bot has been stopped by the admin.\n"
                "📩 *CONTACT ADMIN FOR USE THIS BOT*",
                parse_mode="Markdown"
            )
            return

        # ── Maintenance check (skip for admin) ──
        if not is_any_admin(update.effective_user.id) and is_bot_stopped(cols):
            context.user_data.pop('wait_for', None)
            await update.message.reply_text(
                "🔧 *Bot Under Maintenance*\n\nPlease try again after some time.",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
            return

        # ── Pre-check: credit expiry aware gate ──
        if mode in CREDIT_COSTS:
            u_credits = get_effective_credits(cols, user_info)
            required  = CREDIT_COSTS[mode]
            if u_credits < required:
                context.user_data.pop('wait_for', None)
                await update.message.reply_text(
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:*  `{u_credits} Credits`\n"
                    f"🔍 *Required:*  `{int(required)} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*",
                    reply_markup=InlineKeyboardMarkup([
                        [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                        [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                    ]),
                    parse_mode="Markdown"
                )
                return

        # ── CREATE_BOT (normal user — sirf token, then payment ₹250) ──
        if mode == "CREATE_BOT":
            context.user_data.pop('wait_for', None)
            token = input_txt.strip()
            if not re.match(r'^\d+:[\w-]{35,}$', token):
                await update.message.reply_text(
                    "❌ *Invalid token format!*\n\n"
                    "Sahi format: `1234567890:AAHxxxxxxxxxxxxxxxxxxxxxx`\n\nDobara try karo.",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return
            wait_msg     = await update.message.reply_text("⚙️ Token verify ho raha hai...", quote=True)
            bot_username = await get_bot_username(token)
            if bot_username == "unknown":
                await wait_msg.edit_text(
                    "❌ Token invalid hai ya bot already connected hai.\nPlease check karo aur dobara try karo.",
                    reply_markup=back_to_main_keyboard()
                )
                return
            # Store token for post-payment launch
            context.user_data['pending_bot_token']    = token
            context.user_data['pending_bot_username'] = bot_username
            # Show ₹250 payment details
            detail_text = (
                f"🤖 *Bot Token Verified!*\n\n"
                f"✅ *Bot Username:* @{bot_username}\n\n"
                f"━━━━━━━━━━━━━━━━━━━━\n"
                f"💰 *Bot Activation Fee*\n"
                f"━━━━━━━━━━━━━━━━━━━━\n"
                f"💳 *Amount:* `₹250`\n"
                f"⏰ *Service:* Mirror Bot Lifetime\n\n"
                f"👇 *Payment karo — QR scan karke:*\n"
                f"🏦 *Bank Name:* `DHARMENDRA KUMAR`\n"
                f"📲 *UPI ID:* `vaibhavi.mishra@fam`\n\n"
                f"⚠️ *Exact ₹250 hi bhejo!*"
            )
            kb = InlineKeyboardMarkup([
                [InlineKeyboardButton("✅ I Have Paid", callback_data="PAID_BOT_CREATE")],
                [InlineKeyboardButton("🧑‍💻 Admin Se Contact Karo", url=DEV_CHAT)],
                [InlineKeyboardButton("🔙 Cancel", callback_data="MAIN_MENU")],
            ])
            await wait_msg.delete()
            try:
                async with aiohttp.ClientSession() as session:
                    async with session.get(QR_IMAGE_URL, timeout=aiohttp.ClientTimeout(total=15)) as resp:
                        img_bytes = await resp.read()
                await context.bot.send_photo(
                    chat_id=int(user_id),
                    photo=img_bytes,
                    caption=detail_text,
                    parse_mode="Markdown",
                    reply_markup=kb
                )
            except Exception:
                await context.bot.send_message(
                    chat_id=int(user_id),
                    text=detail_text,
                    parse_mode="Markdown",
                    reply_markup=kb
                )
            return

        # ── CREATE_BOT_MASTER_TOKEN (master admin — sirf token, seedha launch) ──
        if mode == "CREATE_BOT_MASTER_TOKEN":
            context.user_data.pop('wait_for', None)
            token = input_txt.strip()
            if not re.match(r'^\d+:[\w-]{35,}$', token):
                await update.message.reply_text(
                    "❌ *Invalid token format!*\n\nSahi format: `1234567890:AAHxxxxxx`\n\nDobara try karo.",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return
            bot_username = await get_bot_username(token)
            if bot_username == "unknown":
                await update.message.reply_text(
                    "❌ Token invalid hai ya bot already connected hai.",
                    reply_markup=back_to_main_keyboard()
                )
                return
            wait_msg = await update.message.reply_text("⚙️ Bot start ho raha hai...", quote=True)
            success, msg_txt = await launch_user_bot(
                token, int(user_id),
                parent_dev_chat=DEV_CHAT,
                parent_token=cfg["BOT_TOKEN"],
                custom_admin_id=ADMIN_ID
            )
            if success:
                await wait_msg.edit_text(
                    f"✅ *Mirror Bot Ready!*\n\n"
                    f"🤖 Bot: @{bot_username}\n"
                    f"🔑 Token: `{token[:20]}...`\n\n"
                    f"_(Sub-admin set karne ke liye /setadmin use karo)_",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
            else:
                await wait_msg.edit_text(f"❌ Bot start nahi hua: {msg_txt}", reply_markup=back_to_main_keyboard())
            return

        # ── UPI_INFO (raw API response forward) ──
        if mode == "UPI_INFO":
            context.user_data.pop('wait_for', None)
            query = input_txt.strip()
            if not query:
                await update.message.reply_text(
                    "❌ Valid UPI ID ya number do.",
                    reply_markup=back_to_main_keyboard()
                )
                return
            wait_msg = await update.message.reply_text("🔍 Searching UPI Info...", quote=True)

            credit_cost = CREDIT_COSTS.get("UPI_INFO", 2.0)
            u_credits   = get_effective_credits(cols, user_info)
            if u_credits < credit_cost:
                await wait_msg.edit_text(
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:*  `{u_credits} Credits`\n"
                    f"🔍 *Required:*  `{int(credit_cost)} Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*",
                    reply_markup=InlineKeyboardMarkup([
                        [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                        [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                    ]),
                    parse_mode="Markdown"
                )
                return

            url = API_URLS["UPI_INFO"].format(term=query)
            try:
                raw_data = await call_api(url)
            except asyncio.TimeoutError:
                await wait_msg.edit_text(
                    "❌ *API Timeout!*\n\nServer response nahi aa raha. Dobara try karo.\n_(No credit deducted)_",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return
            except Exception as e:
                await wait_msg.edit_text(
                    f"❌ *API Error:* `{str(e)}`\n\n_(No credit deducted)_",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return

            # Raw response ko as-is forward karo — chahe JSON ho ya plain text
            if isinstance(raw_data, dict) or isinstance(raw_data, list):
                raw_text = json.dumps(raw_data, ensure_ascii=False, indent=2)
            else:
                raw_text = str(raw_data).strip()

            raw_text = clean_output(raw_text)

            if not raw_text:
                await wait_msg.edit_text(
                    "⚠️ *DATA NOT AVAILABLE*\n\n🔍 Is UPI ke liye koi data nahi mila.\n\n"
                    f"💳 *Credit Refund:* `{int(credit_cost)} Credit(s)` ✅",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return

            header  = f"*{AVI_HEADER}*\n\n"
            footer  = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
            full_msg = header + f"```\n{raw_text}\n```" + footer

            if len(full_msg) > 4000:
                full_msg = header + f"```\n{raw_text[:3700]}\n...(truncated)\n```" + footer

            try:
                sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
            except Exception:
                try:
                    sent = await wait_msg.edit_text(full_msg, reply_markup=back_to_main_keyboard())
                except Exception:
                    sent = await context.bot.send_message(chat_id=int(user_id), text=full_msg, reply_markup=back_to_main_keyboard())
            asyncio.create_task(auto_delete_message(sent, delay=20))

            # Credit deduct (only on success)
            user_doc_fresh = cols["users"].find_one({"user_id": user_id})
            if get_effective_credits(cols, user_doc_fresh) < 999999.0:
                cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -credit_cost}})
            return

        # ── TG_USERNAME (direct API — no userid lookup needed) ──
        if mode == "TG_USERNAME":
            context.user_data.pop('wait_for', None)
            raw_username = input_txt.strip()
            # Ensure @ prefix for API call
            if not raw_username.startswith('@'):
                raw_username = '@' + raw_username

            if is_protected(cols, raw_username):
                await update.message.reply_text(
                    "❌ Error: This username is restricted. Search not allowed.",
                    reply_markup=back_to_main_keyboard()
                )
                return

            wait_msg = await update.message.reply_text("🔍 Searching...", quote=True)
            await send_api_result(update, context, cols, user_info, "TG_USERNAME", raw_username, wait_msg)
            return

        # ── VEH_TO_NUM ──
        if mode == "VEH_TO_NUM":
            context.user_data.pop('wait_for', None)
            vehicle_num = re.sub(r'\s+', '', input_txt.upper())  # remove spaces, uppercase

            if is_protected(cols, vehicle_num):
                await update.message.reply_text(
                    "❌ Error: This vehicle number is restricted. Search not allowed.",
                    reply_markup=back_to_main_keyboard()
                )
                return

            wait_msg = await update.message.reply_text("🔍 Searching...", quote=True)
            await send_api_result(update, context, cols, user_info, "VEH_TO_NUM", vehicle_num, wait_msg)
            return

        # ── INSTAGRAM ──
        if mode == "INSTAGRAM":
            context.user_data.pop('wait_for', None)
            raw_uname = input_txt.strip().lstrip('@')
            if not raw_uname:
                await update.message.reply_text(
                    "❌ Valid Instagram username do.",
                    reply_markup=back_to_main_keyboard()
                )
                return
            wait_msg = await update.message.reply_text("🔍 Searching Instagram...", quote=True)
            await send_api_result(update, context, cols, user_info, "INSTAGRAM", raw_uname, wait_msg)
            return

        # ── FREE FIRE ──
        if mode == "FREE_FIRE":
            context.user_data.pop('wait_for', None)
            uid = re.sub(r'\D', '', input_txt.strip())
            if not uid:
                await update.message.reply_text(
                    "❌ Valid Free Fire UID do (sirf numbers).\nExample: `2240460996`",
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
                return
            wait_msg = await update.message.reply_text("🔍 Searching Free Fire...", quote=True)
            await send_api_result(update, context, cols, user_info, "FREE_FIRE", uid, wait_msg)
            return

        # ── NUMBER ──
        if mode == "NUMBER":
            cleaned = clean_phone_number(input_txt)
            if len(cleaned) != 10:
                await update.message.reply_text(
                    "❌ Invalid number! Enter a valid 10-digit mobile number.\nEX. 9876543210",
                    reply_markup=back_to_main_keyboard()
                )
                return
            context.user_data.pop('wait_for', None)

            if is_protected(cols, cleaned):
                await update.message.reply_text(
                    "❌ Error: This number is restricted. Search not allowed.",
                    reply_markup=back_to_main_keyboard()
                )
                return

            wait_msg = await update.message.reply_text("🔍 Searching...", quote=True)
            await send_api_result(update, context, cols, user_info, "NUMBER", cleaned, wait_msg)
            return

        # ── PAK INFO ──
        if mode == "PAK_INFO":
            context.user_data.pop('wait_for', None)
            # Clean: strip spaces, dashes, dots, +92 / 0092 / 092 / leading 0
            raw = re.sub(r'[\s\-\.\(\)]', '', input_txt.strip())  # remove spaces & separators
            raw = re.sub(r'^\+', '', raw)                          # remove leading +
            raw = re.sub(r'\D', '', raw)                           # keep only digits
            if raw.startswith('0092'):
                raw = raw[4:]
            elif raw.startswith('92') and len(raw) == 12:
                raw = raw[2:]
            elif raw.startswith('092') and len(raw) == 13:
                raw = raw[3:]
            elif raw.startswith('0') and len(raw) == 11:
                raw = raw[1:]

            if len(raw) != 10 or not raw.startswith('3'):
                await update.message.reply_text(
                    "❌ *Invalid Pakistan number!*\n\n"
                    "10 digit ka number do jo 3 se shuru ho.\n"
                    "Example: `3429533570`",
                    parse_mode="Markdown",
                    reply_markup=back_to_main_keyboard()
                )
                return

            wait_msg = await update.message.reply_text("🔍 Searching Pakistan records...", quote=True)

            # Credit check
            u_credits = get_effective_credits(cols, user_info)
            if u_credits < 2.0:
                await wait_msg.edit_text(
                    "╔══════════════════════════════╗\n"
                    "║    ❌  *INSUFFICIENT CREDIT*  ❌    ║\n"
                    "╚══════════════════════════════╝\n\n"
                    f"💳 *Your Balance:* `{u_credits} Credits`\n"
                    "🔍 *Required:* `2 Credits`\n\n"
                    "📦 *Credit kharido — Full Access Pao!*",
                    parse_mode="Markdown",
                    reply_markup=InlineKeyboardMarkup([
                        [InlineKeyboardButton("💰 BUY CREDIT", callback_data="BUY")],
                        [InlineKeyboardButton("🔙 Back to Menu", callback_data="MAIN_MENU")],
                    ])
                )
                return

            # Call PAK API
            pak_url = f"https://sim-api.fakcloud.tech/?number={raw}"
            try:
                raw_data = await call_api(pak_url)
            except asyncio.TimeoutError:
                await wait_msg.edit_text(
                    "❌ *API Timeout!* Server respond nahi kar raha. Dobara try karo.\n_(No credit deducted)_",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return
            except Exception as e:
                await wait_msg.edit_text(
                    f"❌ *API Error:* `{str(e)}`\n\n_(No credit deducted)_",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return

            # Parse response — structure from screenshot:
            # {"success": true, "data": {"search_type":"phone","records_count":1,"records":[{...}],...}}
            success_flag = raw_data.get("success", False)
            data_block   = raw_data.get("data", {})

            if not success_flag or not data_block:
                err = raw_data.get("message", "No data found.")
                await wait_msg.edit_text(
                    f"❌ *Result not found!*\n\n`{err}`\n\n_(No credit deducted)_",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return

            records = data_block.get("records", [])
            if not records:
                await wait_msg.edit_text(
                    "❌ *No records found for this number.*\n\n_(No credit deducted)_",
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                return

            # Build structured output
            PAK_FIELD_MAP = {
                "full_name":  ("👤", "Full Name"),
                "name":       ("👤", "Name"),
                "phone":      ("📱", "Phone"),
                "mobile":     ("📱", "Mobile"),
                "cnic":       ("📃 🆔", "CNIC"),
                "id":         ("📃 🆔", "Document ID"),
                "id_number":  ("📃 🆔", "Document ID"),
                "address":    ("🏠", "Address"),
                "city":       ("🏙️", "City"),
                "province":   ("🗺️", "Province"),
                "dob":        ("📅", "Date of Birth"),
                "gender":     ("⚧️", "Gender"),
                "operator":   ("📡", "Operator"),
                "email":      ("📧", "Email"),
                "father":     ("👨", "Father Name"),
                "father_name":("👨", "Father Name"),
            }

            record = records[0]
            lines  = []
            for k, v in record.items():
                if v is None or str(v).strip() in ("", "null", "None", "N/A"):
                    continue
                key_lower = k.lower().replace(" ", "_")
                val_str   = str(v).strip()
                if key_lower in PAK_FIELD_MAP:
                    icon, label = PAK_FIELD_MAP[key_lower]
                    lines.append(f"{icon} *{label}:* `{val_str}`")
                else:
                    lines.append(f"• *{k}:* `{val_str}`")

            records_count = data_block.get("records_count", len(records))
            result_body   = "\n".join(lines) if lines else f"`{json.dumps(record, indent=2, ensure_ascii=False)}`"
            if records_count > 1:
                result_body += f"\n\n📊 *Total Records Found: {records_count}*"

            header_text = f"*{AVI_HEADER}*\n🇵🇰 *PAK INFO RESULT*\n\n"
            footer_text = "\n\n⚠️ *Ye result 20 second mein auto-delete ho jayega!*"
            full_msg    = header_text + result_body + footer_text

            if len(full_msg) > 4000:
                full_msg = header_text + result_body[:3900] + "\n\n_(truncated)_" + footer_text

            try:
                sent = await wait_msg.edit_text(full_msg, parse_mode="Markdown", reply_markup=back_to_main_keyboard())
                asyncio.create_task(auto_delete_message(sent, delay=20))
            except Exception:
                sent = await context.bot.send_message(
                    chat_id=int(user_id), text=full_msg,
                    parse_mode="Markdown", reply_markup=back_to_main_keyboard()
                )
                asyncio.create_task(auto_delete_message(sent, delay=20))

            # Deduct credits
            user_doc_fresh = cols["users"].find_one({"user_id": user_id})
            if get_effective_credits(cols, user_doc_fresh) < 999999.0:
                cols["users"].update_one({"user_id": user_id}, {"$inc": {"credits": -2.0}})
            return

        # ── AADHAR, VEHICLE, CAR, FAMILY, RATIONS_INFO, PAN, EMAIL ──
        if mode in ["AADHAR", "VEHICLE", "CAR", "FAMILY", "RATIONS_INFO", "PAN", "EMAIL_LOOKUP"]:
            if is_protected(cols, input_txt):
                await update.message.reply_text(
                    "❌ Error: This input is restricted. Search not allowed.",
                    reply_markup=back_to_main_keyboard()
                )
                context.user_data.pop('wait_for', None)
                return

            context.user_data.pop('wait_for', None)
            wait_msg = await update.message.reply_text("🔍 Searching...", quote=True)
            await send_api_result(update, context, cols, user_info, mode, input_txt, wait_msg)
            return

        # ── FEEDBACK TEXT — user ne feedback likha ──
        if mode == "FEEDBACK_TEXT":
            context.user_data.pop('wait_for', None)
            user      = update.effective_user
            uname_str = f"@{user.username}" if user.username else "No username"
            msg_to_admin = (
                "💬 *New Feedback / Suggestion!*\n\n"
                f"👤 *User:* {user.first_name} ({uname_str})\n"
                f"🆔 *User ID:* `{user.id}`\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                f"📝 *Message:*\n{input_txt}"
            )
            for adm_id in get_all_admin_ids():
                try:
                    await context.bot.send_message(
                        chat_id=adm_id,
                        text=msg_to_admin,
                        parse_mode="Markdown"
                    )
                except Exception:
                    pass
            await update.message.reply_text(
                "✅ *Feedback Submit Ho Gaya!*\n\n"
                "Shukriya! Tumhara suggestion admin tak pahunch gaya. 🙏",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
            return

        # ── BUG REPORT TEXT — user ne bug report likhi ──
        if mode == "BUG_REPORT_TEXT":
            context.user_data.pop('wait_for', None)
            user      = update.effective_user
            uname_str = f"@{user.username}" if user.username else "No username"
            msg_to_admin = (
                "🐛 *Bug Report Received!*\n\n"
                f"👤 *User:* {user.first_name} ({uname_str})\n"
                f"🆔 *User ID:* `{user.id}`\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                f"🔍 *Bug Details:*\n{input_txt}"
            )
            for adm_id in get_all_admin_ids():
                try:
                    await context.bot.send_message(
                        chat_id=adm_id,
                        text=msg_to_admin,
                        parse_mode="Markdown"
                    )
                except Exception:
                    pass
            await update.message.reply_text(
                "✅ *Bug Report Submit Ho Gaya!*\n\n"
                "Shukriya! Admin is issue ko jald fix karega. 🛠️",
                parse_mode="Markdown",
                reply_markup=back_to_main_keyboard()
            )
            return

        context.user_data.pop('wait_for', None)

    # ---------- SETADMIN (parent admin only — mirror bot select karke sub-admin set karo) ----------
    async def setadmin_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        args = context.args

        if not args:
            # No userid — show current sub-admins across all mirror bots
            mirror_tokens = get_mirror_bots_for_parent(cfg["BOT_TOKEN"])
            if not mirror_tokens:
                await update.message.reply_text(
                    "❌ Koi mirror bot nahi mila.\n\nPehle mirror bot banao.",
                    parse_mode="Markdown"
                )
                return
            lines = ["👤 *Mirror Bot Sub-Admins:*\n"]
            for tok in mirror_tokens:
                info  = user_bots.get(tok) or stopped_bots.get(tok) or {}
                uname = info.get("username", "unknown") if isinstance(info, dict) else "unknown"
                m_cols = get_db(stable_db_name(tok))
                doc    = m_cols["users"].find_one({"user_id": "SUB_ADMINS"})
                ids    = doc.get("ids", []) if doc else []
                id_str = ", ".join(f"`{i}`" for i in ids) if ids else "_koi nahi_"
                lines.append(f"🤖 @{uname}: {id_str}")
            lines.append("\n_Usage: `/setadmin <userid>` → mirror bot select karo_")
            await update.message.reply_text("\n".join(lines), parse_mode="Markdown")
            return

        try:
            target_id = int(args[0].strip())
        except ValueError:
            await update.message.reply_text("❌ Valid user ID do (number).", parse_mode="Markdown")
            return

        # Mirror bot list dikhao inline buttons mein
        mirror_tokens = get_mirror_bots_for_parent(cfg["BOT_TOKEN"])
        if not mirror_tokens:
            await update.message.reply_text(
                "❌ Koi mirror bot nahi mila.\n\nPehle mirror bot banao.",
                parse_mode="Markdown"
            )
            return

        keyboard = []
        for tok in mirror_tokens:
            info  = user_bots.get(tok) or stopped_bots.get(tok) or {}
            uname = info.get("username", "unknown") if isinstance(info, dict) else "unknown"
            status = "🟢" if tok in user_bots else "🔴"
            tok_prefix = tok[:15]
            keyboard.append([
                InlineKeyboardButton(
                    f"{status} @{uname}",
                    callback_data=f"SADMIN_SET_{tok_prefix}_{target_id}"
                )
            ])
        keyboard.append([InlineKeyboardButton("❌ Cancel", callback_data="MAIN_MENU")])

        await update.message.reply_text(
            f"👤 *User `{target_id}` ko kis mirror bot ka sub-admin banana hai?*\n\n"
            f"_Niche se bot choose karo:_",
            parse_mode="Markdown",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    # ---------- REMOVEADMIN ----------
    async def removeadmin_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        args = context.args

        if not args:
            await update.message.reply_text(
                "Usage: `/removeadmin <userid>`", parse_mode="Markdown"
            )
            return

        try:
            target_id = int(args[0].strip())
        except ValueError:
            await update.message.reply_text("❌ Valid user ID do.", parse_mode="Markdown")
            return

        # Mirror bot list dikhao
        mirror_tokens = get_mirror_bots_for_parent(cfg["BOT_TOKEN"])
        if not mirror_tokens:
            await update.message.reply_text("❌ Koi mirror bot nahi mila.", parse_mode="Markdown")
            return

        keyboard = []
        for tok in mirror_tokens:
            info  = user_bots.get(tok) or stopped_bots.get(tok) or {}
            uname = info.get("username", "unknown") if isinstance(info, dict) else "unknown"
            status = "🟢" if tok in user_bots else "🔴"
            tok_prefix = tok[:15]
            keyboard.append([
                InlineKeyboardButton(
                    f"{status} @{uname}",
                    callback_data=f"SADMIN_REM_{tok_prefix}_{target_id}"
                )
            ])
        keyboard.append([InlineKeyboardButton("❌ Cancel", callback_data="MAIN_MENU")])

        await update.message.reply_text(
            f"👤 *User `{target_id}` ko kis mirror bot se remove karna hai?*\n\n"
            f"_Niche se bot choose karo:_",
            parse_mode="Markdown",
            reply_markup=InlineKeyboardMarkup(keyboard)
        )

    # ---------- FREE (admin — grant free usage to all users for N hours/days) ----------
    async def free_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
        if not is_any_admin(update.effective_user.id):
            return
        args = context.args
        if not args:
            await update.message.reply_text(
                "❌ *Usage:*\n\n"
                "`/free 2hour` — 2 ghante ki free usage\n"
                "`/free 1day`  — 1 din ki free usage\n"
                "`/free 30min` — 30 minute ki free usage\n\n"
                "Sabhi users ko broadcast hoga aur utne der tak unlimited free search milega.",
                parse_mode="Markdown"
            )
            return

        duration_str = args[0].lower().strip()
        # Parse duration
        match = re.match(r'^(\d+)(min|hour|day)s?$', duration_str)
        if not match:
            await update.message.reply_text(
                "❌ *Invalid format!*\n\nExamples: `2hour`, `1day`, `30min`",
                parse_mode="Markdown"
            )
            return

        qty  = int(match.group(1))
        unit = match.group(2)
        if unit == "min":
            delta = timedelta(minutes=qty)
            label = f"{qty} minute"
        elif unit == "hour":
            delta = timedelta(hours=qty)
            label = f"{qty} ghante"
        else:  # day
            delta = timedelta(days=qty)
            label = f"{qty} din"

        free_until = datetime.utcnow() + delta

        # Save free session in DB (system doc)
        cols["users"].update_one(
            {"user_id": "FREE_SESSION"},
            {"$set": {"active": True, "free_until": free_until}},
            upsert=True
        )

        # Broadcast to all users
        broadcast_text = (
            "╔══════════════════════════════╗\n"
            "║   🎉  *FREE USAGE GRANTED!*  🎉   ║\n"
            "╚══════════════════════════════╝\n\n"
            f"🎁 *Admin ne sabhi users ko free search grant kiya!*\n\n"
            f"⏰ *Free Duration:* `{label}`\n"
            f"🕐 *Valid Until:* `{free_until.strftime('%d %b %Y, %H:%M')} UTC`\n\n"
            "✅ *Ab aap bina credit ke search kar sakte ho!*\n"
            f"_Jaldi karo — sirf {label} tak valid hai!_ 🚀"
        )

        # Collect all users from this bot + mirror bots
        own_users = list(cols["users"].find({}, {"user_id": 1}))
        all_mirror_tokens = get_all_mirror_bots_recursive(cfg["BOT_TOKEN"])

        send_map = {}
        main_uids = [
            u["user_id"] for u in own_users
            if u.get("user_id") and str(u["user_id"]).lstrip("-").isdigit()
        ]
        if main_uids:
            send_map[cfg["BOT_TOKEN"]] = (cfg["BOT_TOKEN"], main_uids)

        for m_token in all_mirror_tokens:
            m_db_name = stable_db_name(m_token)
            m_cols    = get_db(m_db_name)
            m_users   = list(m_cols["users"].find({}, {"user_id": 1}))
            m_uids    = [
                u["user_id"] for u in m_users
                if u.get("user_id") and str(u["user_id"]).lstrip("-").isdigit()
            ]
            if m_uids:
                send_map[m_token] = (m_token, m_uids)

        total_sent = 0
        async with aiohttp.ClientSession() as session:
            for token_str, (tok, uids) in send_map.items():
                for uid in uids:
                    try:
                        await session.post(
                            f"https://api.telegram.org/bot{tok}/sendMessage",
                            json={
                                "chat_id": uid,
                                "text": broadcast_text,
                                "parse_mode": "Markdown"
                            },
                            timeout=aiohttp.ClientTimeout(total=5)
                        )
                        total_sent += 1
                        await asyncio.sleep(0.05)
                    except Exception:
                        pass

        await update.message.reply_text(
            f"✅ *Free Session Active!*\n\n"
            f"⏰ *Duration:* `{label}`\n"
            f"📅 *Until:* `{free_until.strftime('%d %b %Y, %H:%M')} UTC`\n"
            f"📢 *Broadcast sent to:* `{total_sent}` users",
            parse_mode="Markdown"
        )

    return {
        "start":       start,
        "add_credit":  add_credit,
        "remove_sub":  remove_subscription,
        "stats":       stats_cmd,
        "check":       check_cmd,
        "stop":        stop_cmd,
        "run":         run_cmd,
        "broadcast":   broadcast_cmd,
        "mybots":      mybots_cmd,
        "protect":     protect_cmd,
        "unprotect":   unprotect_cmd,
        "redeem":      redeem_cmd,
        "button":      button_handler,
        "message":     handle_message,
        "photo":       handle_photo,
        "setadmin":    setadmin_cmd,
        "removeadmin": removeadmin_cmd,
        "free":        free_cmd,
    }


# ========================================================
# ============= CREATE YOUR OWN BOT =====================
# ========================================================

async def launch_user_bot(token: str, owner_id: int, parent_dev_chat: str = "", parent_token: str = "", custom_admin_id: int = None):
    if token in user_bots:
        return False, "Bot already running with this token."

    db_name  = stable_db_name(token)
    cols     = get_db(db_name)
    dev_chat = parent_dev_chat if parent_dev_chat else f"tg://user?id={owner_id}"

    # Admin ID determine karo:
    # - custom_admin_id diya gaya → use that (parent bot ka ADMIN_ID ya master admin set kiya)
    # - nahi diya → owner_id (fallback)
    effective_admin = custom_admin_id if custom_admin_id else owner_id

    cfg = {
        "BOT_TOKEN":     token,
        "ADMIN_ID":      effective_admin,
        "FORCE_CHANNEL": "",
        "DEV_CHAT":      dev_chat,
        "DB_NAME":       db_name,
    }

    try:
        username = await get_bot_username(token)
        handlers = make_handlers(cfg, cols)
        app = ApplicationBuilder().token(token).build()
        app.add_handler(CommandHandler("start",       handlers["start"]))
        app.add_handler(CommandHandler("add",         handlers["add_credit"]))
        app.add_handler(CommandHandler("remove",      handlers["remove_sub"]))
        app.add_handler(CommandHandler("broadcast",   handlers["broadcast"]))
        app.add_handler(CommandHandler("stats",       handlers["stats"]))
        app.add_handler(CommandHandler("check",       handlers["check"]))
        app.add_handler(CommandHandler("stop",        handlers["stop"]))
        app.add_handler(CommandHandler("run",         handlers["run"]))
        app.add_handler(CommandHandler("protect",     handlers["protect"]))
        app.add_handler(CommandHandler("unprotect",   handlers["unprotect"]))
        app.add_handler(CommandHandler("redeem",      handlers["redeem"]))
        app.add_handler(CommandHandler("mybots",      handlers["mybots"]))
        app.add_handler(CommandHandler("setadmin",    handlers["setadmin"]))
        app.add_handler(CommandHandler("removeadmin", handlers["removeadmin"]))
        app.add_handler(CommandHandler("free",        handlers["free"]))
        app.add_handler(CallbackQueryHandler(handlers["button"]))
        app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handlers["message"]))
        app.add_handler(MessageHandler(filters.PHOTO, handlers["photo"]))
        user_bots[token] = {
            "owner_id":         owner_id,
            "username":         username,
            "parent_token":     parent_token,
            "admin_id":         effective_admin,
            "parent_dev_chat":  parent_dev_chat,
        }
        # Persist to MongoDB so restart can re-launch
        persist_running_bot(token, user_bots[token])
        # If it was previously in stopped_bots, remove it from there
        stopped_bots.pop(token, None)
        remove_persisted_stopped_bot(token)
        asyncio.create_task(run_user_bot(app, token))
        return True, "✅ Bot started successfully!"
    except Exception as e:
        return False, f"❌ Failed: {str(e)}"

async def get_bot_username(token: str) -> str:
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(
                f"https://api.telegram.org/bot{token}/getMe",
                timeout=aiohttp.ClientTimeout(total=10)
            ) as resp:
                data = await resp.json()
                if data.get("ok"):
                    return data["result"].get("username", "unknown")
    except Exception:
        pass
    return "unknown"


async def run_user_bot(app, token):
    try:
        await app.initialize()
        await app.start()
        await app.updater.start_polling(drop_pending_updates=True)
        logging.info(f"✅ User bot running: {token[:20]}...")
        while token in user_bots:
            await asyncio.sleep(1)
        # Token removed from user_bots — gracefully stop
        try:
            await app.updater.stop()
            await app.stop()
            await app.shutdown()
        except Exception as e:
            logging.warning(f"User bot shutdown warning ({token[:20]}): {e}")
    except Exception as e:
        logging.error(f"User bot error ({token[:20]}): {e}")
        user_bots.pop(token, None)


def stable_db_name(token: str) -> str:
    """Python's hash() is randomized per-run. Use hashlib for stable DB names."""
    import hashlib
    h = int(hashlib.md5(token.encode()).hexdigest(), 16) % 100000
    return f"UserBot_{h}"


def get_mirror_bots_for_parent(parent_token: str) -> list:
    """Return list of mirror bot tokens (running + stopped) that directly belong to a given parent bot token."""
    running = [
        tok for tok, info in user_bots.items()
        if isinstance(info, dict) and info.get("parent_token") == parent_token
    ]
    stopped = [
        tok for tok, info in stopped_bots.items()
        if isinstance(info, dict) and info.get("parent_token") == parent_token
    ]
    seen = set()
    result = []
    for tok in running + stopped:
        if tok not in seen:
            seen.add(tok)
            result.append(tok)
    return result


def is_bot_running(token: str) -> bool:
    """Check if a mirror bot is currently running."""
    return token in user_bots


def get_all_mirror_bots_recursive(root_token: str, _visited: set = None) -> list:
    """
    Recursively collect ALL mirror bot tokens in the tree rooted at root_token.
    Example: A → [X, Y, Z], X → [P, Q], Y → [M]
    Result: [X, Y, Z, P, Q, M]
    """
    if _visited is None:
        _visited = set()
    result = []
    direct = get_mirror_bots_for_parent(root_token)
    for tok in direct:
        if tok not in _visited:
            _visited.add(tok)
            result.append(tok)
            result.extend(get_all_mirror_bots_recursive(tok, _visited))
    return result


# ========================================================
# =================== RUN BOT ============================
# ========================================================

async def run_bot(cfg):
    cols     = get_db(cfg["DB_NAME"])
    handlers = make_handlers(cfg, cols)

    app = ApplicationBuilder().token(cfg["BOT_TOKEN"]).build()
    app.add_handler(CommandHandler("start",       handlers["start"]))
    app.add_handler(CommandHandler("add",         handlers["add_credit"]))
    app.add_handler(CommandHandler("remove",      handlers["remove_sub"]))
    app.add_handler(CommandHandler("broadcast",   handlers["broadcast"]))
    app.add_handler(CommandHandler("stats",       handlers["stats"]))
    app.add_handler(CommandHandler("check",       handlers["check"]))
    app.add_handler(CommandHandler("stop",        handlers["stop"]))
    app.add_handler(CommandHandler("run",         handlers["run"]))
    app.add_handler(CommandHandler("protect",     handlers["protect"]))
    app.add_handler(CommandHandler("unprotect",   handlers["unprotect"]))
    app.add_handler(CommandHandler("redeem",      handlers["redeem"]))
    app.add_handler(CommandHandler("mybots",      handlers["mybots"]))
    app.add_handler(CommandHandler("setadmin",    handlers["setadmin"]))
    app.add_handler(CommandHandler("removeadmin", handlers["removeadmin"]))
    app.add_handler(CommandHandler("free",        handlers["free"]))
    app.add_handler(CallbackQueryHandler(handlers["button"]))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handlers["message"]))
    app.add_handler(MessageHandler(filters.PHOTO, handlers["photo"]))

    async with app:
        await app.initialize()
        await app.start()
        await app.updater.start_polling(drop_pending_updates=True)
        logging.info(f"✅ Bot started: {cfg['BOT_TOKEN'][:20]}...")
        while True:
            await asyncio.sleep(1)


# ========================================================
# =================== MAIN ===============================
# ========================================================

async def main():
    keep_alive()
    load_stopped_bots_from_db()          # Restart pe stopped bots restore karo
    await load_and_relaunch_mirror_bots()  # ← Restart pe running mirror bots re-launch
    await asyncio.gather(*[run_bot(cfg) for cfg in BOTS])


if __name__ == "__main__":
    asyncio.run(main())
