from __future__ import annotations

from aiogram import F, Router
from aiogram.filters import CommandStart, CommandObject
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
from sqlalchemy import select

from app import leaderboard as leaderboard_svc, runtime
from app.common import (
    apply_topup,
    get_or_create_user,
    is_admin,
    money,
    notify_admins,
    parse_amount,
)
from app.config import get_settings
from app.handlers.catalog import build_item_card
from app.db import get_session
from app.keyboards import (
    crypto_invoice_kb,
    main_menu,
    purchases_2fa_kb,
    topup_review_kb,
)
from app.models import Purchase, TopUp, User
from app.services import crypto_pay, totp
from app.states import TopUpStates

router = Router()


def _parse_start_arg(command: CommandObject | None) -> tuple[int | None, int | None]:
    """Returns (referrer_id, item_id) parsed from the /start deep-link payload."""
    if not command or not command.args:
        return None, None
    arg = command.args.strip()
    if arg.startswith("item"):
        rest = arg[4:]
        return None, (int(rest) if rest.isdigit() else None)
    if arg.startswith("ref"):
        arg = arg[3:]
    return (int(arg) if arg.isdigit() else None), None


@router.message(CommandStart())
async def cmd_start(message: Message, command: CommandObject, state: FSMContext) -> None:
    await state.clear()
    settings = get_settings()
    referrer_id, item_id = _parse_start_arg(command)
    async with get_session() as session:
        user = await get_or_create_user(session, message.from_user, referrer_id)
        if user.is_banned:
            await message.answer("🚫 You are banned.")
            return
        card = (
            await build_item_card(session, item_id, settings.currency)
            if item_id
            else None
        )
    await message.answer(
        f"👋 Hi, <b>{message.from_user.full_name}</b>!\n\n"
        "Welcome to the shop. Pick a section below 👇",
        reply_markup=main_menu(is_admin(message.from_user.id)),
    )
    if card is not None:
        text, kb = card
        await message.answer(text, reply_markup=kb)


@router.message(F.text == "◇ Profile")
async def profile(message: Message) -> None:
    settings = get_settings()
    async with get_session() as session:
        user = await get_or_create_user(session, message.from_user)
        bot_user = await message.bot.me()
        ref_link = f"https://t.me/{bot_user.username}?start=ref{user.user_id}"
        text = (
            "👤 <b>Profile</b>\n\n"
            f"🆔 ID: <code>{user.user_id}</code>\n"
            f"💰 Balance: <b>{money(user.balance)} {settings.currency}</b>\n"
            f"🧾 Purchases: <b>{user.purchases_count}</b>\n\n"
            "🎁 <b>Referral program</b>\n"
            f"Invited: <b>{user.referred_count}</b>\n"
            f"Earned: <b>{money(user.referral_earned)} {settings.currency}</b>\n"
            f"You get {settings.referral_percent}% of every top-up your friends make.\n\n"
            f"🔗 Your link:\n{ref_link}"
        )
    await message.answer(text)


@router.message(F.text == "≡ My purchases")
async def my_purchases(message: Message) -> None:
    settings = get_settings()
    async with get_session() as session:
        result = await session.execute(
            select(Purchase)
            .where(Purchase.user_id == message.from_user.id)
            .order_by(Purchase.created_at.desc())
            .limit(15)
        )
        purchases = result.scalars().all()
    if not purchases:
        await message.answer("🧾 You have no purchases yet.")
        return
    lines = ["🧾 <b>Recent purchases:</b>\n"]
    twofa_rows: list[tuple[str, int]] = []
    for p in purchases:
        date = p.created_at.strftime("%d.%m.%Y %H:%M")
        lock = " 🔐" if p.totp_secret else ""
        lines.append(
            f"• <b>{p.item_name}</b>{lock} — {money(p.price)} {settings.currency} ({date})\n"
            f"<code>{p.content}</code>"
        )
        if p.totp_secret:
            twofa_rows.append((p.item_name, p.id))
    kb = purchases_2fa_kb(twofa_rows) if twofa_rows else None
    await message.answer("\n\n".join(lines), reply_markup=kb)


@router.message(F.text == "★ Leaderboard")
async def leaderboard(message: Message) -> None:
    settings = get_settings()
    async with get_session() as session:
        result = await session.execute(
            select(User)
            .where(User.weekly_spent > 0)
            .order_by(User.weekly_spent.desc())
            .limit(10)
        )
        top = result.scalars().all()
    days_left = leaderboard_svc.days_left()
    reward_line = (
        f"🎁 Weekly rewards: 🥇 +{money(leaderboard_svc.REWARD_1)} · "
        f"🥈 +{money(leaderboard_svc.REWARD_2)} {settings.currency} to balance\n"
        f"⏳ Resets in <b>{days_left}</b> day(s)\n"
    )
    if not top:
        await message.answer(
            "🏆 <b>Weekly leaderboard</b>\n\n"
            + reward_line
            + "\nNo purchases this week yet — be the first!"
        )
        return
    medals = {1: "🥇", 2: "🥈", 3: "🥉"}
    lines = ["🏆 <b>Top buyers this week</b>\n", reward_line]
    for i, u in enumerate(top, 1):
        rank = medals.get(i, f"<b>{i}.</b>")
        name = u.username and f"@{u.username}"
        if not name:
            uid = str(u.user_id)
            name = f"{uid[:3]}***{uid[-2:]}" if len(uid) > 5 else "user"
        lines.append(f"{rank} {name} — <b>{money(u.weekly_spent)} {settings.currency}</b>")
    await message.answer("\n".join(lines))


@router.callback_query(F.data.startswith("otp:"))
async def get_otp(callback: CallbackQuery) -> None:
    purchase_id = int(callback.data.split(":")[1])
    async with get_session() as session:
        purchase = await session.get(Purchase, purchase_id)
    if purchase is None or purchase.user_id != callback.from_user.id:
        await callback.answer("Purchase not found", show_alert=True)
        return
    if not purchase.totp_secret:
        await callback.answer("This item has no 2FA attached", show_alert=True)
        return
    try:
        code, remaining = totp.generate(purchase.totp_secret)
    except Exception:
        await callback.answer("❌ Error: invalid 2FA secret", show_alert=True)
        return
    pretty = f"{code[:3]} {code[3:]}" if len(code) == 6 else code
    await callback.answer(
        f"🔐 2FA code: {pretty}\n⏳ Valid for {remaining} more sec.",
        show_alert=True,
    )


# ---------- Balance top-up ----------


@router.message(F.text == "➕ Add balance")
async def topup_start(message: Message, state: FSMContext) -> None:
    await state.set_state(TopUpStates.amount)
    await message.answer("💰 Enter the amount to add (whole number).")


@router.message(TopUpStates.amount)
async def topup_amount(message: Message, state: FSMContext) -> None:
    amount = parse_amount(message.text or "")
    if amount is None:
        await message.answer("❌ Enter a positive number (e.g. 10 or 0.5).")
        return
    await state.clear()
    # Auto-payment via CryptoBot when a token is configured; manual approval otherwise.
    if runtime.crypto_enabled():
        await _start_crypto_topup(message, amount)
    else:
        await _start_manual_topup(message, amount)


async def _start_crypto_topup(message: Message, amount: float) -> None:
    settings = get_settings()
    asset = runtime.crypto_asset()
    async with get_session() as session:
        await get_or_create_user(session, message.from_user)
        topup = TopUp(
            user_id=message.from_user.id, amount=amount, status="pending", method="crypto"
        )
        session.add(topup)
        await session.commit()
        topup_id = topup.id
    try:
        invoice = await crypto_pay.create_invoice(
            amount=amount,
            asset=asset,
            description=f"Add balance: {money(amount)} {settings.currency}",
            payload=str(topup_id),
        )
    except Exception:
        await message.answer(
            "❌ Failed to create the invoice — check the CryptoBot token. "
            "Try again later."
        )
        return
    pay_url = (
        invoice.get("bot_invoice_url")
        or invoice.get("pay_url")
        or invoice.get("mini_app_invoice_url", "")
    )
    if not pay_url:
        await message.answer("❌ Payment link unavailable. Try again later.")
        return
    async with get_session() as session:
        row = await session.get(TopUp, topup_id)
        if row:
            row.invoice_id = str(invoice.get("invoice_id", ""))
            await session.commit()
    await message.answer(
        f"💳 <b>Payment via CryptoBot</b>\n\n"
        f"Amount: <b>{money(amount)} {asset}</b>\n\n"
        "1. Tap <b>Pay</b> and complete the payment in @CryptoBot.\n"
        "2. Then tap <b>I paid — check</b> to credit your balance automatically.",
        reply_markup=crypto_invoice_kb(pay_url, topup_id),
    )


async def _start_manual_topup(message: Message, amount: float) -> None:
    settings = get_settings()
    async with get_session() as session:
        await get_or_create_user(session, message.from_user)
        topup = TopUp(
            user_id=message.from_user.id, amount=amount, status="pending", method="manual"
        )
        session.add(topup)
        await session.commit()
        topup_id = topup.id
    await message.answer(
        f"✅ Balance request for <b>{money(amount)} {settings.currency}</b> created.\n"
        "Please wait for an administrator to confirm."
    )
    uname = f"@{message.from_user.username}" if message.from_user.username else "—"
    await notify_admins(
        message.bot,
        "🔔 <b>New balance request</b>\n\n"
        f"👤 {message.from_user.full_name} ({uname})\n"
        f"🆔 <code>{message.from_user.id}</code>\n"
        f"💰 Amount: <b>{money(amount)} {settings.currency}</b>",
        reply_markup=topup_review_kb(topup_id),
    )


@router.callback_query(F.data.startswith("checkpay:"))
async def topup_check(callback: CallbackQuery) -> None:
    topup_id = int(callback.data.split(":")[1])
    settings = get_settings()
    async with get_session() as session:
        topup = await session.get(TopUp, topup_id)
        if topup is None or topup.user_id != callback.from_user.id:
            await callback.answer("Request not found", show_alert=True)
            return
        if topup.status == "approved":
            await callback.answer("✅ Already credited", show_alert=True)
            return
        if not topup.invoice_id:
            await callback.answer("Invoice missing", show_alert=True)
            return

        try:
            invoice = await crypto_pay.get_invoice(topup.invoice_id)
        except Exception:
            await callback.answer("Check failed, try again", show_alert=True)
            return
        if not invoice or invoice.get("status") != "paid":
            await callback.answer("⏳ Payment not received yet.", show_alert=True)
            return

        topup.status = "approved"
        result = await apply_topup(session, topup)
        if result is None:
            await callback.answer("User error", show_alert=True)
            return
        new_balance, bonus, referrer_id = result
        await session.commit()

    await callback.message.edit_text(
        f"✅ <b>Payment received!</b>\n"
        f"💰 Balance: {money(new_balance)} {settings.currency}"
    )
    await callback.answer("Credited ✅")
    if referrer_id and bonus > 0:
        try:
            await callback.bot.send_message(
                referrer_id,
                f"🎁 Referral bonus: +{money(bonus)} {settings.currency} "
                "for a top-up by an invited user!",
            )
        except Exception:
            pass
