
import asyncio
import logging
import os
import secrets
from aiohttp import web
from deposit_provider import CryptAPIDeposit
from datetime import date
from dotenv import load_dotenv
from aiogram import Bot, Dispatcher, F
from aiogram.enums import ParseMode
from aiogram.client.default import DefaultBotProperties
from aiogram.filters import CommandStart, Command
from aiogram.types import Message, CallbackQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from db import DB

load_dotenv()
TOKEN = os.getenv("BOT_TOKEN", "")
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "Kiaw_prvv").lstrip("@").lower()
SUPPORT = os.getenv("SUPPORT_USERNAME", "support")
CURRENCY = os.getenv("CURRENCY", "USD")

if not TOKEN:
    raise RuntimeError("BOT_TOKEN is missing in .env")

db = DB("bot.db")
dp = Dispatcher()
deposit_providers = {"": CryptAPIDeposit(""), "BEP20": CryptAPIDeposit("BEP20")}
admin_state = {}  # telegram_id -> expected admin input

def is_admin(user):
    return bool(user and (user.username or "").lower() == ADMIN_USERNAME)

def home_kb():
    b = InlineKeyboardBuilder()
    b.button(text="🛒 Store", callback_data="store")
    b.adjust(1)
    b.button(text="👤 My Profile", callback_data="profile")
    b.button(text="📦 My Orders", callback_data="orders")
    b.adjust(2)
    b.button(text="🌎 ML Region", callback_data="ml_region")
    b.button(text="ℹ️ Support", callback_data="support")
    b.adjust(1)
    if ADMIN_USERNAME:
        b.button(text="⚙️ Admin Panel", callback_data="admin")
    return b.as_markup()

def store_kb(page=1):
    cats = [
        ("💥 PUBG UC (AUTO)", "cat:pubg"),
        ("🇨🇳 PUBG UC CN WEB (AUTO)", "cat:pubg_cn"),
        ("🎁 PUBG UC SPECIAL PACK (AUTO)", "cat:pubg_special"),
        ("💎 MOBILE LEGEND (GLOBAL)", "cat:ml"),
        ("🔥 FREEFIRE DIAMOND (AUTO)", "cat:ff"),
        ("🌎 MOBILE LEGEND (OTHER REGIONS)", "cat:ml_other"),
        ("🏎️ RACING MASTER (AUTO)", "cat:racing"),
        ("🕵️ Identity V (AUTO)", "cat:identity"),
        ("🚄 HONKAI STAR RAIL (AUTO)", "cat:hsr"),
        ("🩸 BLOOD STRIKE", "cat:blood"),
        ("🎁 ML FIRST BONUS PACKS", "cat:ml_bonus"),
        ("💖 LOVE AND DEEPSPACE", "cat:love"),
    ]
    pages = 1
    b = InlineKeyboardBuilder()
    for text, data in cats:
        b.button(text=text, callback_data=data)
    b.adjust(1)
    b.button(text="🏠 Home", callback_data="home")
    return b.as_markup()

PRODUCTS = {
    "pubg": [("60 UC", "60", 0.99), ("325 UC", "325", 4.99), ("660 UC", "660", 9.49), ("1800 UC", "1800", 24.99), ("3850 UC", "3850", 49.99), ("8100 UC", "8100", 99.99)],
    "pubg_cn": [("60 UC", "60", 1.19), ("325 UC", "325", 5.29), ("660 UC", "660", 9.99)],
    "pubg_special": [("Special Pack 1", "sp1", 2.99), ("Special Pack 2", "sp2", 9.99)],
    "ml": [("86 Diamonds", "86", 1.49), ("172 Diamonds", "172", 2.89), ("257 Diamonds", "257", 4.19), ("706 Diamonds", "706", 10.49)],
    "ff": [("100 Diamonds", "100", 0.99), ("310 Diamonds", "310", 2.89), ("520 Diamonds", "520", 4.69), ("1060 Diamonds", "1060", 9.29)],
    "ml_other": [("86 Diamonds", "86", 1.69), ("172 Diamonds", "172", 3.19)],
    "racing": [("60 Coins", "60", 0.99), ("300 Coins", "300", 4.49)],
    "identity": [("60 Echoes", "60", 0.99), ("300 Echoes", "300", 4.49)],
    "hsr": [("60 Oneiric Shard", "60", 0.99), ("300 Oneiric Shard", "300", 4.49)],
    "blood": [("60 Credits", "60", 0.99), ("300 Credits", "300", 4.49)],
    "ml_bonus": [("First Bonus 86", "86", 1.49), ("First Bonus 172", "172", 2.89)],
    "love": [("60 Diamonds", "60", 0.99), ("300 Diamonds", "300", 4.49)],
}
NAMES = {
    "pubg":"💥 PUBG UC (AUTO)", "pubg_cn":"🇨🇳 PUBG UC CN WEB (AUTO)",
    "pubg_special":"🎁 PUBG UC SPECIAL PACK (AUTO)", "ml":"💎 MOBILE LEGEND (GLOBAL)",
    "ff":"🔥 FREEFIRE DIAMOND (AUTO)", "ml_other":"🌎 MOBILE LEGEND (OTHER REGIONS)",
    "racing":"🏎️ RACING MASTER (AUTO)", "identity":"🕵️ Identity V (AUTO)",
    "hsr":"🚄 HONKAI STAR RAIL (AUTO)", "blood":"🩸 BLOOD STRIKE",
    "ml_bonus":"🎁 ML FIRST BONUS PACKS", "love":"💖 LOVE AND DEEPSPACE"
}

def category_kb(cat):
    b=InlineKeyboardBuilder()
    for name, sku, price in PRODUCTS.get(cat, []):
        b.button(text=f"{name} • ${price:.2f}", callback_data=f"buy:{cat}:{sku}")
    b.adjust(1)
    b.button(text="⬅️ Back", callback_data="store")
    return b.as_markup()

def profile_kb():
    b=InlineKeyboardBuilder()
    b.button(text="➕ Add balance", callback_data="add_balance")
    b.button(text="📜 Order history", callback_data="orders")
    b.adjust(1)
    b.button(text="🧾 Request Receipt", callback_data="receipt")
    b.button(text="₮ USDT Deposits", callback_data="deposits")
    b.button(text="💳 Payment history", callback_data="payments")
    b.adjust(2)
    b.button(text="🔌 API access", callback_data="api")
    b.button(text="🏠 Main menu", callback_data="home")
    b.adjust(1)
    return b.as_markup()

def admin_kb():
    b=InlineKeyboardBuilder()
    b.button(text="📊 Statistics", callback_data="adm:stats")
    b.button(text="👥 Users", callback_data="adm:users")
    b.adjust(2)
    b.button(text="📦 Orders", callback_data="adm:orders")
    b.button(text="💳 Payments", callback_data="adm:payments")
    b.adjust(2)
    b.button(text="₮ Deposits", callback_data="adm:deposits")
    b.button(text="🛒 Products", callback_data="adm:products")
    b.adjust(2)
    b.button(text="💰 Balance", callback_data="adm:balance")
    b.button(text="📢 Broadcast", callback_data="adm:broadcast")
    b.button(text="🔑 API Keys", callback_data="adm:apikeys")
    b.adjust(2)
    b.button(text="🏠 Main menu", callback_data="home")
    b.adjust(1)
    return b.as_markup()

def admin_back_kb():
    b=InlineKeyboardBuilder()
    b.button(text="⚙️ Admin Panel", callback_data="admin")
    b.button(text="🏠 Main menu", callback_data="home")
    b.adjust(1)
    return b.as_markup()

def user_actions_kb(uid):
    b=InlineKeyboardBuilder()
    b.button(text="➕ Add $", callback_data=f"adm:add:{uid}")
    b.button(text="➖ Remove $", callback_data=f"adm:sub:{uid}")
    b.adjust(2)
    b.button(text="📦 Orders", callback_data=f"adm:uorders:{uid}")
    b.button(text="💳 Payments", callback_data=f"adm:upay:{uid}")
    b.adjust(2)
    b.button(text="⚙️ Admin Panel", callback_data="admin")
    b.adjust(1)
    return b.as_markup()

async def home(message_or_cb, user):
    text = (
        "╔════════════════════════════╗\n"
        "║ 🎮🎮 Gamers Workshop       ║\n"
        "╚════════════════════════════╝\n\n"
        f"👋 Welcome, @{user['username'] or 'User'}!\n\n"
        f"💰 Balance: ${user['balance']:.2f} {CURRENCY}\n"
        f"📦 Total Orders: {user['orders']}\n"
        f"📅 Today's Spent: ${db.today_spent(user['id']):.2f}\n\n"
        "⚡ Fast Delivery • 🔒 Secure • ✅ Trusted"
    )
    if is_admin(user):
        text += "\n\n⚙️ Admin access enabled."
    if isinstance(message_or_cb, Message):
        await message_or_cb.answer(text, reply_markup=home_kb())
    else:
        await message_or_cb.message.edit_text(text, reply_markup=home_kb())

@dp.message(CommandStart())
async def start(message: Message):
    user = db.get_or_create_user(message.from_user)
    await home(message, user)

@dp.message(Command("admin"))
async def admin_cmd(message: Message):
    user=db.get_or_create_user(message.from_user)
    if not is_admin(message.from_user):
        return await message.answer("⛔ Access denied.")
    await message.answer("⚙️ <b>Admin Panel</b>\n\nChoose an action:", reply_markup=admin_kb())

@dp.callback_query(F.data == "home")
async def cb_home(c: CallbackQuery):
    user=db.get_or_create_user(c.from_user)
    await home(c, user); await c.answer()

@dp.callback_query(F.data == "admin")
async def cb_admin(c: CallbackQuery):
    if not is_admin(c.from_user):
        return await c.answer("⛔ Access denied.", show_alert=True)
    await c.message.edit_text("⚙️ <b>Admin Panel</b>\n\nChoose an action:", reply_markup=admin_kb())
    await c.answer()

@dp.callback_query(F.data == "store")
async def cb_store(c: CallbackQuery):
    await c.message.edit_text("🎮 Choose a game\n📄 Page 1 / 1", reply_markup=store_kb(1)); await c.answer()

@dp.callback_query(F.data.startswith("cat:"))
async def cb_cat(c: CallbackQuery):
    cat=c.data.split(":",1)[1]
    await c.message.edit_text(f"🛒 {NAMES.get(cat, cat)}\n\nChoose a product:", reply_markup=category_kb(cat)); await c.answer()

@dp.callback_query(F.data.startswith("buy:"))
async def cb_buy(c: CallbackQuery):
    _,cat,sku=c.data.split(":")
    item=next((x for x in PRODUCTS[cat] if x[1]==sku), None)
    if not item: return await c.answer("Product not found", show_alert=True)
    name,_,price=item
    b=InlineKeyboardBuilder()
    b.button(text="✅ Confirm order", callback_data=f"confirm:{cat}:{sku}")
    b.button(text="❌ Cancel", callback_data=f"cat:{cat}")
    b.adjust(1)
    await c.message.edit_text(f"🛍️ Order preview\n\n🎮 {NAMES[cat]}\n📦 Product: {name}\n💵 Price: ${price:.2f}\n\nPress confirm to continue.", reply_markup=b.as_markup())
    await c.answer()

@dp.callback_query(F.data.startswith("confirm:"))
async def cb_confirm(c: CallbackQuery):
    _,cat,sku=c.data.split(":")
    item=next((x for x in PRODUCTS[cat] if x[1]==sku), None)
    if not item: return await c.answer("Product not found", show_alert=True)
    name,_,price=item; user=db.get_or_create_user(c.from_user)
    if user["balance"] < price:
        await c.message.edit_text(f"❌ Insufficient balance.\n\nRequired: ${price:.2f}\nYour balance: ${user['balance']:.2f}", reply_markup=profile_kb())
        return await c.answer()
    order_id=db.create_order(user["id"], NAMES[cat], name, price)
    db.change_balance(user["id"], -price)
    await c.message.edit_text(f"✅ Order #{order_id} created.\n\n📦 {name}\n💵 ${price:.2f}\n⏳ Status: Processing", reply_markup=home_kb())
    await c.answer()

@dp.callback_query(F.data == "profile")
async def cb_profile(c: CallbackQuery):
    user=db.get_or_create_user(c.from_user)
    used=db.region_used_today(user["id"])
    text=(f"👤 USER: @{user['username'] or 'N/A'}\nTelegram ID: {user['telegram_id']}\n\n"
          f"💰 Balance\n└ ${user['balance']:.2f}\n\n"
          f"📊 Spending\n└ Total (all time): ${db.total_spent(user['id']):.2f}\n└ Today: ${db.today_spent(user['id']):.2f}\n\n"
          f"📦 Orders\n└ Completed (all time): {db.completed_count(user['id'])}\n└ Processing: {db.processing_count(user['id'])}\n\n"
          f"🌎 ML Region Verify\n└ Today: {used}/20 free used · left {max(0,20-used)} · then $0.03")
    await c.message.edit_text(text, reply_markup=profile_kb()); await c.answer()

@dp.callback_query(F.data == "orders")
async def cb_orders(c: CallbackQuery):
    user=db.get_or_create_user(c.from_user); rows=db.orders(user["id"],10)
    text="📦 My Orders\n\n"+("\n".join(f"#{r['id']} • {r['product']} • ${r['price']:.2f} • {r['status']}" for r in rows) if rows else "No orders yet.")
    b=InlineKeyboardBuilder(); b.button(text="🏠 Home", callback_data="home")
    await c.message.edit_text(text, reply_markup=b.as_markup()); await c.answer()

@dp.callback_query(F.data == "add_balance")
async def cb_add(c: CallbackQuery):
    b=InlineKeyboardBuilder()
    b.button(text="🟢 USDT BEP20 (BNB Smart Chain)", callback_data="deposit:BEP20")
    b.button(text="🔵 USDT  ()", callback_data="deposit:")
    b.button(text="🏠 Home", callback_data="home")
    b.adjust(1)
    await c.message.edit_text(
        "➕ <b>Add Balance</b>\n\nChoose the USDT network you will use.\n\n"
        "🟢 <b>BEP20</b> = BNB Smart Chain / compatible with Trust Wallet\n"
        "🔵 <b></b> = \n\n"
        "⚠️ The sending network must exactly match the selected network.",
        reply_markup=b.as_markup()
    )
    await c.answer()

@dp.callback_query(F.data.startswith("deposit:"))
async def cb_deposit(c: CallbackQuery):
    network=c.data.split(":",1)[1].upper()
    if network not in deposit_providers:
        return await c.answer("Unsupported network", show_alert=True)
    provider=deposit_providers[network]
    user=db.get_or_create_user(c.from_user)
    if not provider.ready():
        env_name="USDT_BEP20_FORWARD_ADDRESS" if network=="BEP20" else "USDT__FORWARD_ADDRESS"
        await c.message.edit_text(
            f"⚠️ {network} deposit is not configured yet.\n\n"
            f"Admin must set {env_name} and PUBLIC_BASE_URL in .env.",
            reply_markup=profile_kb()
        )
        return await c.answer()
    dep=db.get_deposit(user["id"], network)
    if not dep:
        try:
            nonce=secrets.token_urlsafe(32)
            data=await provider.create_address(user["id"], nonce)
            address=data["address_in"]
            callback=data.get("callback_url","")
            db.create_deposit(user["id"], "CryptAPI", network, "USDT", address, callback, nonce)
            dep=db.get_deposit(user["id"], network)
        except Exception:
            logging.exception("deposit address creation failed")
            await c.message.edit_text("❌ Could not create your deposit address right now.\n\nPlease try again later.", reply_markup=profile_kb())
            return await c.answer()
    network_text="BEP20 (BNB Smart Chain)" if network=="BEP20" else " ()"
    await c.message.edit_text(
        "₮ <b>USDT Deposit</b>\n\n"
        f"Network: <b>{network_text}</b>\n"
        f"Your personal deposit address:\n<code>{dep['address_in']}</code>\n\n"
        f"⚠️ Send <b>USDT {network} only</b> to this address.\n"
        "The bot will detect the payment automatically and credit your balance after the required confirmations.\n\n"
        f"💵 Current balance: <b>${user['balance']:.2f}</b>",
        reply_markup=profile_kb()
    )
    await c.answer()

@dp.callback_query(F.data == "deposits")
async def cb_deposits(c: CallbackQuery):
    user=db.get_or_create_user(c.from_user)
    rows=db.deposits(user["id"], 10)
    if not rows:
        text="₮ <b>USDT Deposits</b>\\n\\nNo deposit address created yet."
    else:
        lines=[]
        for r in rows:
            lines.append(
                f"#{r['id']} • {r['status']}\\n"
                f"Address: <code>{r['address_in']}</code>\\n"
                f"Confirmed: {float(r['confirmed_amount']):.6f} USDT\\n"
                f"Credited: ${float(r['credited_amount']):.2f}"
            )
        text="₮ <b>USDT Deposits</b>\\n\\n" + "\\n\\n".join(lines)
    b=InlineKeyboardBuilder(); b.button(text="➕ Get deposit address", callback_data="add_balance"); b.button(text="🏠 Home", callback_data="home"); b.adjust(1)
    await c.message.edit_text(text, reply_markup=b.as_markup()); await c.answer()

@dp.callback_query(F.data == "payments")
async def cb_payments(c: CallbackQuery):
    user=db.get_or_create_user(c.from_user); rows=db.payments(user["id"])
    text="💳 Payment history\n\n"+("\n".join(f"#{x['id']} • ${x['amount']:.2f} • {x['status']}" for x in rows) if rows else "No payments yet.")
    b=InlineKeyboardBuilder(); b.button(text="🏠 Main menu", callback_data="home")
    await c.message.edit_text(text, reply_markup=b.as_markup()); await c.answer()

@dp.callback_query(F.data == "receipt")
async def cb_receipt(c: CallbackQuery):
    await c.message.edit_text(f"🧾 Receipt request\n\nPlease contact @{SUPPORT} and send your order number.", reply_markup=profile_kb()); await c.answer()

@dp.callback_query(F.data == "api")
async def cb_api(c: CallbackQuery):
    user=db.get_or_create_user(c.from_user); key=db.get_api_key(user["id"])
    await c.message.edit_text(f"🔌 API access\n\nYour API key:\n`{key}`", parse_mode=ParseMode.MARKDOWN, reply_markup=profile_kb()); await c.answer()

@dp.callback_query(F.data == "ml_region")
async def cb_region(c: CallbackQuery):
    user=db.get_or_create_user(c.from_user); used=db.region_used_today(user["id"])
    if used < 20:
        db.use_region(user["id"]); msg=f"🌎 ML Region Verify\n\nFree verification used: {used+1}/20\nRemaining: {19-used}"
    else: msg="🌎 ML Region Verify\n\nDaily free limit reached.\nNext verification price: $0.03"
    await c.message.edit_text(msg, reply_markup=home_kb()); await c.answer()

@dp.callback_query(F.data == "support")
async def cb_support(c: CallbackQuery):
    await c.message.edit_text(f"ℹ️ Support\n\nContact: @{SUPPORT}", reply_markup=home_kb()); await c.answer()

# ---------------- ADMIN PANEL ----------------

@dp.callback_query(F.data == "adm:stats")
async def adm_stats(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    s=db.stats()
    text=(f"📊 <b>Statistics</b>\n\n👥 Users: {s['users']}\n📦 Orders: {s['orders']}\n"
          f"⏳ Processing: {s['processing']}\n✅ Completed: {s['completed']}\n"
          f"💵 Completed sales: ${s['sales']:.2f}\n💳 Payments: ${s['payments']:.2f}")
    await c.message.edit_text(text, reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:users")
async def adm_users(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    rows=db.all_users(20)
    text="👥 <b>Users</b>\n\n"+("\n".join(f"#{r['id']} • @{r['username'] or 'N/A'} • ID {r['telegram_id']} • ${r['balance']:.2f}" for r in rows) if rows else "No users.")
    await c.message.edit_text(text+"\n\nبرای مدیریت موجودی از بخش Balance استفاده کن.", reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:orders")
async def adm_orders(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    rows=db.all_orders(20)
    text="📦 <b>Recent Orders</b>\n\n"+("\n".join(f"#{r['id']} • {r['product']} • ${r['price']:.2f} • {r['status']} • @{r['username'] or 'N/A'}" for r in rows) if rows else "No orders.")
    await c.message.edit_text(text, reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:payments")
async def adm_payments(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    rows=db.all_payments(20)
    text="💳 <b>Payments</b>\n\n"+("\n".join(f"#{r['id']} • ${r['amount']:.2f} • {r['status']} • @{r['username'] or 'N/A'}" for r in rows) if rows else "No payments.")
    await c.message.edit_text(text, reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:balance")
async def adm_balance(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    admin_state[c.from_user.id]="balance_user"
    await c.message.edit_text("💰 <b>Balance Manager</b>\n\nTelegram ID کاربر را بفرست.\nمثال: <code>411181273</code>", reply_markup=admin_back_kb())
    await c.answer()

@dp.callback_query(F.data.startswith("adm:add:"))
async def adm_add(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    uid=int(c.data.split(":")[2]); admin_state[c.from_user.id]=f"add:{uid}"
    await c.message.edit_text("➕ مبلغ افزایش موجودی را به دلار بفرست.\nمثال: <code>25</code>", reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data.startswith("adm:sub:"))
async def adm_sub(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    uid=int(c.data.split(":")[2]); admin_state[c.from_user.id]=f"sub:{uid}"
    await c.message.edit_text("➖ مبلغ کسر موجودی را به دلار بفرست.\nمثال: <code>10</code>", reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data.startswith("adm:uorders:"))
async def adm_uorders(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    uid=int(c.data.split(":")[2]); rows=db.orders(uid,20)
    text="📦 User Orders\n\n"+("\n".join(f"#{r['id']} • {r['product']} • ${r['price']:.2f} • {r['status']}" for r in rows) if rows else "No orders.")
    await c.message.edit_text(text, reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data.startswith("adm:upay:"))
async def adm_upay(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    uid=int(c.data.split(":")[2]); rows=db.payments(uid)
    text="💳 User Payments\n\n"+("\n".join(f"#{r['id']} • ${r['amount']:.2f} • {r['status']}" for r in rows) if rows else "No payments.")
    await c.message.edit_text(text, reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:products")
async def adm_products(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    text="🛒 <b>Products</b>\n\n"
    for cat, items in PRODUCTS.items():
        text += f"<b>{NAMES[cat]}</b>\n" + "\n".join(f"• {name} — ${price:.2f} (SKU {sku})" for name,sku,price in items) + "\n\n"
    await c.message.edit_text(text[:4000], reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:deposits")
async def adm_deposits(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    rows=db.all_deposits(30)
    if not rows:
        text="₮ <b>USDT Deposits</b>\\n\\nNo deposits yet."
    else:
        text="₮ <b>Recent USDT Deposits</b>\\n\\n" + "\\n\\n".join(
            f"#{r['id']} • @{r['username'] or 'N/A'} • {r['status']}\\n"
            f"{float(r['confirmed_amount']):.6f} USDT → ${float(r['credited_amount']):.2f}\\n"
            f"<code>{r['address_in']}</code>"
            for r in rows
        )
    await c.message.edit_text(text[:4000], reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:broadcast")
async def adm_broadcast(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    admin_state[c.from_user.id]="broadcast"
    await c.message.edit_text("📢 <b>Broadcast</b>\n\nمتن پیام را بفرست تا برای کاربران ثبت‌شده ارسال شود.", reply_markup=admin_back_kb()); await c.answer()

@dp.callback_query(F.data == "adm:apikeys")
async def adm_apikeys(c: CallbackQuery):
    if not is_admin(c.from_user): return await c.answer("⛔ Access denied.", show_alert=True)
    rows=db.api_users(20)
    text="🔑 <b>API Keys</b>\n\n"+("\n".join(f"@{r['username'] or 'N/A'} • {r['api_key']}" for r in rows) if rows else "No API keys.")
    await c.message.edit_text(text[:4000], reply_markup=admin_back_kb()); await c.answer()

@dp.message()
async def text_router(message: Message):
    user=db.get_or_create_user(message.from_user)
    state=admin_state.get(message.from_user.id)
    if not is_admin(message.from_user) or not state:
        return
    if state=="balance_user":
        try:
            tg_id=int(message.text.strip())
        except:
            return await message.answer("❌ Telegram ID نامعتبر است.")
        target=db.find_user(tg_id)
        if not target:
            admin_state.pop(message.from_user.id,None)
            return await message.answer("❌ کاربر پیدا نشد.", reply_markup=admin_back_kb())
        admin_state[message.from_user.id]=f"balance:{target['id']}"
        await message.answer(f"👤 @{target['username'] or 'N/A'}\n💰 Current balance: ${target['balance']:.2f}\n\nمقدار جدید موجودی را بفرست:")
        return
    if state.startswith("balance:"):
        uid=int(state.split(":")[1])
        try: amount=float(message.text.strip())
        except: return await message.answer("❌ عدد نامعتبر است.")
        if amount < 0: return await message.answer("❌ مبلغ منفی مجاز نیست.")
        db.set_balance(uid, amount); admin_state.pop(message.from_user.id,None)
        target=db.user_by_id(uid)
        return await message.answer(f"✅ موجودی @{target['username'] or 'N/A'} به ${amount:.2f} تغییر کرد.", reply_markup=admin_back_kb())
    if state.startswith("add:") or state.startswith("sub:"):
        action, uid = state.split(":")
        try: amount=float(message.text.strip())
        except: return await message.answer("❌ عدد نامعتبر است.")
        if amount <= 0: return await message.answer("❌ مبلغ باید بیشتر از صفر باشد.")
        db.change_balance(int(uid), amount if action=="add" else -amount)
        admin_state.pop(message.from_user.id,None)
        target=db.user_by_id(int(uid))
        return await message.answer(f"✅ Balance updated.\n@{target['username'] or 'N/A'} → ${target['balance']:.2f}", reply_markup=admin_back_kb())
    if state=="broadcast":
        text=message.text
        admin_state.pop(message.from_user.id,None)
        users=db.telegram_ids()
        sent=0
        bot=message.bot
        for tg_id in users:
            try:
                await bot.send_message(tg_id, text)
                sent += 1
            except Exception:
                pass
        return await message.answer(f"📢 Broadcast finished.\nSent: {sent}", reply_markup=admin_back_kb())


async def cryptapi_webhook(request):
    try:
        data = await request.json()
    except Exception:
        data = dict(request.query)
    user_id = int(request.query.get("user_id", "0"))
    nonce = request.query.get("nonce", "")
    if not user_id or not nonce:
        return web.Response(status=400, text="bad request")
    dep = db.get_deposit(user_id)
    if not dep or dep["nonce"] != nonce:
        return web.Response(status=403, text="forbidden")

    address = data.get("address_in")
    if address and address != dep["address_in"]:
        return web.Response(status=400, text="address mismatch")

    uuid = str(data.get("uuid") or data.get("txid_in") or "")
    if uuid and db.get_deposit_by_uuid(uuid):
        return web.Response(status=200, text="*ok*")

    value = deposit_provider.webhook_amount(data)
    confirmations = int(float(data.get("confirmations", 0) or 0))
    pending = str(data.get("pending", "")).lower() in ("1", "true", "yes")
    txid = str(data.get("txid_in") or "")
    status = "CONFIRMING" if pending or confirmations < deposit_provider.confirmations else "CONFIRMED"

    # Amount is USDT. Conversion to store USD is configurable through USDT_USD_RATE.
    rate = float(os.getenv("USDT_USD_RATE", "1"))
    usd_amount = value * rate

    if status == "CONFIRMED" and usd_amount > 0:
        changed = db.mark_deposit(dep["id"], status, 0, value, txid, uuid)
        if changed:
            credited_user = db.credit_deposit_once(dep["id"], usd_amount)
            if credited_user:
                # Notification is best-effort; webhook must always be acknowledged.
                try:
                    await request.app["bot"].send_message(
                        db.user_by_id(credited_user)["telegram_id"],
                        f"✅ <b>USDT payment confirmed</b>\n\n"
                        f"₮ Amount: {value:.6f} USDT\n"
                        f"💰 Credited: ${usd_amount:.2f}\n"
                        f"🧾 TX: <code>{txid}</code>"
                    )
                except Exception:
                    logging.exception("user notification failed")
    else:
        db.mark_deposit(dep["id"], status, value, 0, txid, uuid)

    return web.Response(status=200, text="*ok*")

async def start_webhook_server(bot):
    app = web.Application()
    app["bot"] = bot
    app.router.add_post("/webhook/cryptapi", cryptapi_webhook)
    app.router.add_get("/webhook/cryptapi", cryptapi_webhook)
    runner = web.AppRunner(app)
    await runner.setup()
    host = os.getenv("WEBHOOK_HOST", "0.0.0.0")
    port = int(os.getenv("WEBHOOK_PORT", "8080"))
    site = web.TCPSite(runner, host, port)
    await site.start()
    logging.info("Webhook server listening on %s:%s", host, port)
    return runner

async def main():
    logging.basicConfig(level=logging.INFO)
    bot=Bot(TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
    runner = await start_webhook_server(bot)
    try:
        await dp.start_polling(bot)
    finally:
        await runner.cleanup()
        await bot.session.close()

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