"""Deposit flow.

Crypto (TRC20 / BEP20 / BTC) — AUTOMATIC:
  pick method -> enter USD amount -> bot generates a UNIQUE on-chain
  amount + address + 30-min expiry. The chain_watcher service polls free
  public APIs, matches the inbound tx against the unique amount, and
  auto-credits the user's wallet. No admin involvement.

Binance UID — MANUAL (unchanged):
  pick Binance -> enter USD amount -> show UID -> user uploads proof ->
  admin /approve credits wallet.

Telegram Stars (unchanged):
  pick Stars -> enter USD amount -> user sends Stars to admin -> admin
  /approve credits wallet.
"""
from telegram import Update, InputFile
from telegram.ext import (ContextTypes, ConversationHandler, MessageHandler,
                          CallbackQueryHandler, filters)
import io, asyncio
from datetime import datetime, timedelta, timezone
from database.db import DB
from config import PAYMENT_METHODS, ADMIN_USERNAME, STARS_PER_USD
from utils.keyboards import inline, decorate
from utils.helpers import qr_png, money
from services.notifications import notify_staff
from services.rewards import mark_active_and_reward
from services.price import usd_to_btc

CHOOSING, AWAIT_AMOUNT, AWAIT_PROOF = range(3)

# How long an auto-pay request is valid before being marked expired.
EXPIRY_MINUTES = 30

AUTO_METHODS = {"trc20", "bep20", "btc"}


# --------------------------------------------------------------------------
# Unique-amount generation
# --------------------------------------------------------------------------

def _unique_usdt(base_usd: float, dep_id: int) -> float:
    """Add a tiny per-deposit offset so each deposit's USDT amount is unique.

    Adds between 0.000100 and 0.000999 USDT (sub-cent). Total max ≈ $0.001
    extra — invisible to the user, but uniquely identifies the deposit.
    """
    offset = (dep_id % 900 + 100) / 1_000_000  # 0.000100 - 0.000999
    return round(base_usd + offset, 6)


async def _unique_btc(base_usd: float, dep_id: int) -> float:
    """Convert USD to BTC and embed a unique sub-satoshi-group offset."""
    btc = await usd_to_btc(base_usd)
    # Use 8 decimals (satoshis). Slot 100-999 sats.
    sats = int(round(btc * 10**8))
    sats = (sats // 1000) * 1000 + (dep_id % 900 + 100)
    return round(sats / 10**8, 8)


def _expiry_iso() -> str:
    return (datetime.now(timezone.utc) + timedelta(minutes=EXPIRY_MINUTES))\
        .strftime("%Y-%m-%d %H:%M:%S")


# --------------------------------------------------------------------------
# Conversation entry
# --------------------------------------------------------------------------

async def start_add_funds(update: Update, context: ContextTypes.DEFAULT_TYPE):
    rows = [[(decorate(m["label"], i), f"dep:m:{k}")]
            for i, (k, m) in enumerate(PAYMENT_METHODS.items())]
    rows.append([("⬅ Back", "menu:home")])
    if update.callback_query:
        await update.callback_query.answer()
        await update.callback_query.edit_message_text(
            "💳 <b>Select a payment method:</b>\n\n"
            "🟢 USDT TRC20 / BEP20 &amp; ₿ Bitcoin are <b>auto-detected</b> "
            "on-chain — no admin wait.\n"
            "🟡 Bybit UID requires manual approval.",
            parse_mode="HTML", reply_markup=inline(rows))
    else:
        await update.message.reply_html(
            "💳 <b>Select a payment method:</b>", reply_markup=inline(rows))
    return CHOOSING


async def chose_method(update: Update, context: ContextTypes.DEFAULT_TYPE):
    q = update.callback_query; await q.answer()
    key = q.data.split(":")[2]
    method = PAYMENT_METHODS[key]
    context.user_data["dep_method"] = key

    if key == "stars":
        prompt = (
            "⭐ <b>Telegram Stars Deposit</b>\n\n"
            "Enter the <b>USD amount</b> you want to deposit (e.g. <code>5</code>).\n\n"
            f"Rate: <b>$1 = {STARS_PER_USD} ⭐ Stars</b>"
        )
    elif key in AUTO_METHODS:
        prompt = (
            f"<b>{method['label']}</b>  ⚡ <i>Auto-detect</i>\n\n"
            "Enter the <b>USD amount</b> you want to deposit (e.g. <code>10</code>).\n\n"
            "You'll get a unique payment amount &amp; address. Send exactly that "
            "amount within <b>30 minutes</b> — your wallet will be credited "
            "automatically once the network confirms it."
        )
    else:
        prompt = (
            f"<b>{method['label']}</b>\n\n"
            "Enter the <b>USD amount</b> you want to deposit (e.g. <code>5.00</code>).\n"
            "You'll see the payment address on the next step."
        )
    await q.edit_message_text(prompt, parse_mode="HTML",
        reply_markup=inline([[("⬅ Cancel", "dep:cancel")]]))
    return AWAIT_AMOUNT


async def got_amount(update: Update, context: ContextTypes.DEFAULT_TYPE):
    try:
        amount = float(update.message.text.replace("$", "").strip())
        if amount <= 0: raise ValueError
    except (ValueError, AttributeError):
        await update.message.reply_text("Please send a valid positive number (e.g. 5.00).")
        return AWAIT_AMOUNT

    key = context.user_data.get("dep_method")
    if not key:
        await update.message.reply_text("Session expired. Open 💳 Deposit again.")
        return ConversationHandler.END
    context.user_data["dep_amount"] = amount

    # ---------------- Stars ----------------
    if key == "stars":
        return await _create_stars_deposit(update, context, amount)

    # ---------------- Auto crypto ----------------
    if key in AUTO_METHODS:
        return await _create_auto_crypto_deposit(update, context, key, amount)

    # ---------------- Binance (manual) ----------------
    method = PAYMENT_METHODS[key]
    addr = method["address"] or "(not configured — contact admin)"
    text = (
        f"<b>{method['label']}</b>\n\n"
        f"💰 Amount to send: <b>{money(amount)}</b>\n"
        f"📥 Send to UID:\n<code>{addr}</code>\n\n"
        "After sending, reply here with your <b>Transaction ID</b> "
        "(as text) or upload a <b>screenshot</b> of the payment.\n\n"
        "⚠ Bybit UID deposits are reviewed manually by an admin."
    )
    await update.message.reply_html(text,
        reply_markup=inline([[("⬅ Cancel", "dep:cancel")]]))
    return AWAIT_PROOF


# --------------------------------------------------------------------------
# Auto crypto
# --------------------------------------------------------------------------

async def _create_auto_crypto_deposit(update, context, key: str, amount: float):
    method = PAYMENT_METHODS[key]
    addr = method["address"]
    if not addr:
        await update.message.reply_text(
            f"⚠ {method['label']} address is not configured. Contact admin.")
        return ConversationHandler.END

    # Insert first to get a deposit id, then compute the unique amount.
    cur = await DB.execute(
        "INSERT INTO deposits(user_id, method, amount, status, address, expires_at) "
        "VALUES (?,?,?,?,?,?)",
        (update.effective_user.id, key, amount, "pending_payment", addr, _expiry_iso()))
    dep_id = cur.lastrowid

    if key == "btc":
        unique = await _unique_btc(amount, dep_id)
        symbol = "BTC"
        amount_str = f"{unique:.8f}"
    else:
        unique = _unique_usdt(amount, dep_id)
        symbol = "USDT"
        amount_str = f"{unique:.6f}".rstrip("0").rstrip(".")
        # Always keep at least 6 decimals so the offset is visible & matchable.
        if "." not in amount_str:
            amount_str += ".000000"
        else:
            # pad to 6 decimals
            whole, frac = amount_str.split(".")
            amount_str = f"{whole}.{frac.ljust(6, '0')}"

    await DB.execute(
        "UPDATE deposits SET unique_amount=? WHERE id=?", (unique, dep_id))

    text = (
        f"<b>{method['label']}</b>  —  Deposit <b>#{dep_id}</b>\n\n"
        f"💵 USD value: <b>{money(amount)}</b>\n"
        f"📥 Send <b>EXACTLY</b>:\n"
        f"<code>{amount_str}</code> <b>{symbol}</b>\n\n"
        f"📬 To address:\n<code>{addr}</code>\n\n"
        f"⏳ Expires in: <b>{EXPIRY_MINUTES} minutes</b>\n"
        f"🔄 Status: <i>Pending Payment</i>\n\n"
        f"⚠ Send the <b>exact</b> amount (the trailing decimals identify your "
        f"deposit). Wrong amounts cannot be auto-matched."
    )
    sent = await update.message.reply_html(text,
        reply_markup=inline([
            [("🔄 Check Status", f"dep:status:{dep_id}")],
            [("❌ Cancel", "dep:cancel")],
        ]))
    # Send QR with amount-aware payload when possible.
    try:
        if key == "btc":
            qr_payload = f"bitcoin:{addr}?amount={amount_str}"
        else:
            qr_payload = addr
        await update.message.reply_photo(
            photo=InputFile(io.BytesIO(qr_png(qr_payload)), filename="qr.png"),
            caption=f"QR: {method['label']}")
    except Exception:
        pass

    # Persist message coords for the watcher to edit on completion/expiry.
    await DB.execute(
        "UPDATE deposits SET chat_id=?, message_id=? WHERE id=?",
        (sent.chat_id, sent.message_id, dep_id))

    await notify_staff(context.bot,
        f"🆕 <b>Auto Deposit #{dep_id}</b>\n"
        f"User: <code>{update.effective_user.id}</code>\n"
        f"Method: {key.upper()}\n"
        f"Expected: <code>{amount_str} {symbol}</code> ({money(amount)})")

    context.user_data.clear()
    return ConversationHandler.END


# --------------------------------------------------------------------------
# Status check button
# --------------------------------------------------------------------------

_STATUS_LABELS = {
    "pending_payment":   "⏳ Pending Payment",
    "payment_detected":  "🔎 Payment Detected",
    "confirming":        "🔁 Confirming",
    "payment_completed": "✅ Payment Completed",
    "expired":           "⌛ Expired",
    "failed":            "❌ Failed",
    "pending":           "⏳ Pending (manual)",
    "approved":          "✅ Approved",
    "rejected":          "❌ Rejected",
}


async def status_cb(update: Update, context: ContextTypes.DEFAULT_TYPE):
    q = update.callback_query
    try:
        dep_id = int(q.data.split(":")[2])
    except Exception:
        await q.answer("Bad request"); return
    d = await DB.fetchone(
        "SELECT id, user_id, method, amount, unique_amount, status, expires_at, "
        "       detected_txid FROM deposits WHERE id=?", (dep_id,))
    if not d or d["user_id"] != q.from_user.id:
        await q.answer("Not found", show_alert=True); return
    label = _STATUS_LABELS.get(d["status"], d["status"])
    txt = (f"Deposit #{d['id']}\n"
           f"Method: {d['method'].upper()}\n"
           f"Amount: {money(d['amount'])}\n"
           f"Status: {label}")
    if d["detected_txid"]:
        txt += f"\nTX: {d['detected_txid'][:24]}…"
    if d["expires_at"]:
        txt += f"\nExpires: {d['expires_at']} UTC"
    await q.answer(txt, show_alert=True)


# --------------------------------------------------------------------------
# Stars
# --------------------------------------------------------------------------

async def _create_stars_deposit(update, context, amount: float):
    stars = int(round(amount * STARS_PER_USD))
    uid = update.effective_user.id
    ref = f"stars-{stars}-{amount:.2f}"
    try:
        cur = await DB.execute(
            "INSERT INTO deposits(user_id, method, amount, tx_reference, proof_type, status) "
            "VALUES (?,?,?,?,?,?)", (uid, "stars", amount, ref, "stars", "pending"))
        dep_id = cur.lastrowid
    except Exception:
        await update.message.reply_text("⚠ Could not create deposit. Try again.")
        return ConversationHandler.END

    text = (
        f"⭐ <b>Stars Deposit #{dep_id}</b>\n\n"
        f"Amount: <b>{money(amount)}</b>\n"
        f"Send: <b>{stars} ⭐ Telegram Stars</b>\n"
        f"To: 👉 @{ADMIN_USERNAME}\n\n"
        f"After sending, tap <b>Message Admin</b> below or contact "
        f"@{ADMIN_USERNAME} so your wallet gets credited."
    )
    kb = inline([
        [(f"💬 Contact @{ADMIN_USERNAME}", f"https://t.me/{ADMIN_USERNAME}")],
        [("📩 Message Admin in Bot", "sup:contact")],
        [("🏠 Main Menu", "menu:home")],
    ])
    await update.message.reply_html(text, reply_markup=kb)

    u = update.effective_user
    await notify_staff(context.bot,
        f"⭐ <b>New Stars Deposit #{dep_id}</b>\n"
        f"User: @{u.username or '—'} (<code>{u.id}</code>)\n"
        f"Amount: <b>{money(amount)}</b> ({stars} ⭐)\n\n"
        f"Approve after receiving:\n"
        f"<code>/approve {dep_id}</code>  or  <code>/reject {dep_id} reason</code>")
    context.user_data.clear()
    return ConversationHandler.END


# --------------------------------------------------------------------------
# Binance proof (manual)
# --------------------------------------------------------------------------

async def got_proof(update: Update, context: ContextTypes.DEFAULT_TYPE):
    uid = update.effective_user.id
    method = context.user_data.get("dep_method")
    amount = context.user_data.get("dep_amount")
    if not method or not amount:
        await update.message.reply_text("Session expired. Open 💳 Deposit again.")
        return ConversationHandler.END

    if update.message.photo:
        ref = update.message.photo[-1].file_id
        proof_type = "photo"
    elif update.message.text:
        ref = update.message.text.strip()[:200]
        proof_type = "text"
    else:
        await update.message.reply_text("Please send a TX ID or a screenshot.")
        return AWAIT_PROOF

    try:
        cur = await DB.execute(
            "INSERT INTO deposits(user_id, method, amount, tx_reference, proof_type, status) "
            "VALUES (?,?,?,?,?,?)", (uid, method, amount, ref, proof_type, "pending"))
        dep_id = cur.lastrowid
    except Exception:
        await update.message.reply_text(
            "⚠ This transaction reference was already submitted.")
        return ConversationHandler.END

    await update.message.reply_html(
        f"✅ <b>Deposit #{dep_id}</b> submitted for {money(amount)} via {method}.\n"
        "An admin will review and approve it shortly.",
        reply_markup=inline([[("🏠 Main Menu", "menu:home")]]))

    u = update.effective_user
    caption = (
        f"💰 <b>New Deposit #{dep_id}</b>\n"
        f"User: @{u.username or '—'} (<code>{u.id}</code>)\n"
        f"Method: {method}\nAmount: {money(amount)}\n"
        f"Proof: {proof_type}\nRef: <code>{ref[:80]}</code>\n\n"
        f"Admin: <code>/approve {dep_id}</code> or <code>/reject {dep_id} reason</code>"
    )
    if proof_type == "photo":
        from config import STAFF_GROUP_ID
        if STAFF_GROUP_ID:
            try:
                await context.bot.send_photo(STAFF_GROUP_ID, ref, caption=caption,
                                             parse_mode="HTML")
            except Exception:
                pass
    else:
        await notify_staff(context.bot, caption)
    context.user_data.clear()
    return ConversationHandler.END


async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if update.callback_query:
        await update.callback_query.answer()
        await update.callback_query.edit_message_text("Deposit cancelled.")
    else:
        await update.message.reply_text("Deposit cancelled.")
    context.user_data.clear()
    return ConversationHandler.END


def conversation():
    return ConversationHandler(
        entry_points=[
            CallbackQueryHandler(start_add_funds, pattern=r"^wallet:add$"),
            CallbackQueryHandler(start_add_funds, pattern=r"^menu:deposit$"),
        ],
        states={
            CHOOSING:     [CallbackQueryHandler(chose_method, pattern=r"^dep:m:")],
            AWAIT_AMOUNT: [MessageHandler(filters.TEXT & ~filters.COMMAND, got_amount)],
            AWAIT_PROOF:  [MessageHandler((filters.TEXT | filters.PHOTO) & ~filters.COMMAND, got_proof)],
        },
        fallbacks=[CallbackQueryHandler(cancel, pattern=r"^dep:cancel$")],
        per_message=False,
        allow_reentry=True,
    )


def status_handler():
    return CallbackQueryHandler(status_cb, pattern=r"^dep:status:\d+$")


# --------------------------------------------------------------------------
# Admin deposit actions (Binance / Stars only — crypto auto-approves)
# --------------------------------------------------------------------------
from utils.decorators import admin_only

@admin_only
async def approve_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text("Usage: /approve <deposit_id>"); return
    dep_id = int(context.args[0])
    dep = await DB.fetchone("SELECT * FROM deposits WHERE id=?", (dep_id,))
    if not dep:
        await update.message.reply_text("Deposit not found."); return
    if dep["method"] in AUTO_METHODS:
        await update.message.reply_text(
            f"ℹ #{dep_id} is an auto-detect ({dep['method'].upper()}) deposit. "
            "It's handled automatically by the chain watcher — no manual approval needed."
        ); return
    if dep["status"] not in ("pending",):
        await update.message.reply_text(
            f"Deposit already processed (status: {dep['status']})."); return
    await DB.execute(
        "UPDATE deposits SET status='approved', reviewed_at=datetime('now') WHERE id=?", (dep_id,))
    await DB.execute(
        "UPDATE users SET balance = balance + ?, total_deposits = total_deposits + ? "
        "WHERE user_id=?", (dep["amount"], dep["amount"], dep["user_id"]))
    await update.message.reply_text(f"✅ Deposit #{dep_id} approved.")
    try:
        await context.bot.send_message(
            dep["user_id"],
            f"✅ Your deposit of {money(dep['amount'])} was approved and added to your wallet.")
    except Exception:
        pass
    await notify_staff(context.bot,
        f"✅ <b>Deposit Approved</b> #{dep_id} — {money(dep['amount'])} for "
        f"<code>{dep['user_id']}</code>")
    await mark_active_and_reward(context.bot, dep["user_id"])


@admin_only
async def reject_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text("Usage: /reject <deposit_id> [reason]"); return
    dep_id = int(context.args[0])
    reason = " ".join(context.args[1:]) or "No reason provided"
    dep = await DB.fetchone("SELECT * FROM deposits WHERE id=?", (dep_id,))
    if not dep:
        await update.message.reply_text("Deposit not found."); return
    if dep["method"] in AUTO_METHODS:
        await update.message.reply_text(
            f"ℹ #{dep_id} is auto-detect — it'll auto-expire if no payment arrives."); return
    if dep["status"] not in ("pending",):
        await update.message.reply_text("Already processed."); return
    await DB.execute(
        "UPDATE deposits SET status='rejected', admin_note=?, reviewed_at=datetime('now') "
        "WHERE id=?", (reason, dep_id))
    await update.message.reply_text(f"❌ Deposit #{dep_id} rejected.")
    try:
        await context.bot.send_message(
            dep["user_id"],
            f"❌ Your deposit of {money(dep['amount'])} was rejected.\nReason: {reason}")
    except Exception:
        pass
    await notify_staff(context.bot,
        f"❌ <b>Deposit Rejected</b> #{dep_id} — <code>{dep['user_id']}</code>\nReason: {reason}")
