"""Payment routes: create payment via NOWPayments, IPN webhook, status polling"""
import os
import json
import logging
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException, Request, Depends, Header
from typing import Optional

from models import CreatePaymentRequest, CreatePaymentResponse
import nowpayments_client as nowp
from email_service import send_payment_received_email, send_admin_new_application_email
from db import get_db

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/payments", tags=["payments"])

PUBLIC_URL = os.environ.get("PUBLIC_URL", "")


async def _resolve_pricing(app_doc: dict, coupon_code: Optional[str], db) -> dict:
    """Return {price_amount, base_amount, discount_amount, coupon_code} for the application.

    Re-reads current settings (in case admin updated fee).
    """
    settings = await db.settings.find_one({"id": "global"}, {"_id": 0})
    base = float(settings.get("agent_fee_usd", 300.0)) if settings else 300.0
    discount_pct = 0.0
    used_code = None
    if coupon_code:
        code = coupon_code.strip().upper()
        coupon = await db.coupons.find_one({"code": code}, {"_id": 0})
        if not coupon:
            raise HTTPException(status_code=400, detail="Invalid coupon code")
        if not coupon.get("active", True):
            raise HTTPException(status_code=400, detail="Coupon inactive")
        if coupon.get("max_uses", 0) > 0 and coupon.get("used_count", 0) >= coupon["max_uses"]:
            raise HTTPException(status_code=400, detail="Coupon limit reached")
        expires_at = coupon.get("expires_at")
        if expires_at:
            try:
                exp = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
                if exp < datetime.now(timezone.utc):
                    raise HTTPException(status_code=400, detail="Coupon expired")
            except ValueError:
                pass
        discount_pct = float(coupon["discount_percent"])
        used_code = coupon["code"]
    discount_amount = round(base * (discount_pct / 100.0), 2)
    price = max(round(base - discount_amount, 2), 1.0)
    return {
        "price_amount": price,
        "base_amount": base,
        "discount_amount": discount_amount,
        "coupon_code": used_code,
    }


@router.post("/create", response_model=CreatePaymentResponse)
async def create_payment(req: CreatePaymentRequest, db=Depends(get_db)):
    app_doc = await db.applications.find_one({"id": req.application_id}, {"_id": 0})
    if not app_doc:
        raise HTTPException(status_code=404, detail="Application not found")
    if app_doc.get("status") in ("paid", "approved"):
        raise HTTPException(status_code=400, detail="Application already paid")

    pricing = await _resolve_pricing(app_doc, req.coupon_code, db)

    # Pre-check minimum amount accepted by NOWPayments for this network so we can
    # show a friendly bilingual error instead of a raw gateway 400.
    min_amount = await nowp.get_min_amount(req.pay_currency)
    if min_amount and pricing["price_amount"] < min_amount:
        raise HTTPException(
            status_code=400,
            detail=(
                f"Amount ${pricing['price_amount']:.2f} is below the network minimum of ~{min_amount:.2f} USDT for "
                f"{req.pay_currency.upper()}. Try a different network or remove/reduce the coupon discount. / "
                f"এই নেটওয়ার্কে সর্বনিম্ন {min_amount:.2f} USDT পাঠাতে হবে।"
            ),
        )

    callback_url = f"{(await nowp.get_public_url()).rstrip('/') or PUBLIC_URL.rstrip('/')}/api/payments/webhook"
    try:
        nowp_resp = await nowp.create_payment(
            price_amount=pricing["price_amount"],
            pay_currency=req.pay_currency,
            order_id=app_doc["id"],
            order_description=f"Maxlife88 Agent Application - {app_doc['full_name']}",
            ipn_callback_url=callback_url,
        )
    except RuntimeError as e:
        err_str = str(e)
        logger.error("Payment creation failed: %s", err_str)
        # Translate common NOWPayments errors into friendly bilingual messages
        lower = err_str.lower()
        if "amount_minimal" in lower or "less than minimal" in lower:
            raise HTTPException(
                status_code=400,
                detail=(
                    "Amount too small for this network. Please pick a different network or "
                    "reduce the coupon discount. / এই নেটওয়ার্কের জন্য পরিমাণ কম। অন্য নেটওয়ার্ক চেষ্টা করুন।"
                ),
            )
        if "invalid_currency" in lower or "currency" in lower and "not" in lower:
            raise HTTPException(status_code=400, detail="Selected network is currently unavailable. Please choose another.")
        raise HTTPException(status_code=502, detail=f"Payment gateway error: {err_str}")

    payment_id = str(nowp_resp.get("payment_id"))
    pay_address = nowp_resp.get("pay_address")
    pay_amount = float(nowp_resp.get("pay_amount") or 0)
    pay_currency = nowp_resp.get("pay_currency") or req.pay_currency

    update = {
        "payment_id": payment_id,
        "payment_status": nowp_resp.get("payment_status", "waiting"),
        "pay_address": pay_address,
        "pay_amount": pay_amount,
        "pay_currency": pay_currency,
        "price_amount": pricing["price_amount"],
        "base_amount": pricing["base_amount"],
        "discount_amount": pricing["discount_amount"],
        "coupon_code": pricing["coupon_code"],
        "updated_at": datetime.now(timezone.utc).isoformat(),
    }
    await db.applications.update_one({"id": app_doc["id"]}, {"$set": update})

    return CreatePaymentResponse(
        payment_id=payment_id,
        pay_address=pay_address,
        pay_amount=pay_amount,
        pay_currency=pay_currency,
        price_amount=pricing["price_amount"],
        base_amount=pricing["base_amount"],
        discount_amount=pricing["discount_amount"],
        coupon_code=pricing["coupon_code"],
        status=nowp_resp.get("payment_status", "waiting"),
        expires_at=nowp_resp.get("expiration_estimate_date"),
    )


@router.get("/min-amount")
async def get_min_amount(pay_currency: str, db=Depends(get_db)):
    """Public — returns the minimum amount NOWPayments will accept for this network.

    Used by the frontend to show a hint and disable the Generate button when total < min.
    Cached lightly via in-process call (NOWPayments updates infrequently)."""
    if pay_currency not in ("usdtbsc", "usdttrc20"):
        raise HTTPException(status_code=400, detail="Unsupported network")
    min_amount = await nowp.get_min_amount(pay_currency)
    return {"pay_currency": pay_currency, "min_amount_usdt": min_amount or 0}


@router.get("/status/{application_id}")
async def get_payment_status_for_app(application_id: str, db=Depends(get_db)):
    app_doc = await db.applications.find_one({"id": application_id}, {"_id": 0})
    if not app_doc:
        raise HTTPException(status_code=404, detail="Application not found")
    if not app_doc.get("payment_id"):
        return {
            "application_status": app_doc["status"],
            "payment_status": None,
            "has_payment": False,
        }

    # Refresh from NOWPayments
    try:
        live = await nowp.get_payment_status(app_doc["payment_id"])
        live_status = live.get("payment_status")
    except Exception as e:
        logger.warning("Could not refresh payment status: %s", e)
        live_status = app_doc.get("payment_status")
        live = {}

    # Self-heal: if NOWPayments says paid but DB still pending → apply payment
    if nowp.is_paid_status(live_status) and app_doc.get("status") == "pending_payment":
        await _apply_payment_success(app_doc["id"], live_status, db)
        app_doc = await db.applications.find_one({"id": application_id}, {"_id": 0})
    elif live_status and live_status != app_doc.get("payment_status"):
        await db.applications.update_one(
            {"id": application_id},
            {"$set": {"payment_status": live_status, "updated_at": datetime.now(timezone.utc).isoformat()}},
        )
        app_doc["payment_status"] = live_status

    return {
        "application_id": application_id,
        "application_status": app_doc.get("status"),
        "payment_status": app_doc.get("payment_status"),
        "pay_address": app_doc.get("pay_address"),
        "pay_amount": app_doc.get("pay_amount"),
        "pay_currency": app_doc.get("pay_currency"),
        "price_amount": app_doc.get("price_amount"),
        "discount_amount": app_doc.get("discount_amount"),
        "coupon_code": app_doc.get("coupon_code"),
        "actually_paid": (live or {}).get("actually_paid"),
        "has_payment": True,
    }


async def _apply_payment_success(application_id: str, payment_status: str, db):
    """Idempotently mark application paid, increment coupon usage, send emails."""
    app_doc = await db.applications.find_one({"id": application_id}, {"_id": 0})
    if not app_doc:
        return
    if app_doc.get("status") in ("paid", "approved"):
        # already credited - just ensure payment_status updated
        await db.applications.update_one(
            {"id": application_id},
            {"$set": {"payment_status": payment_status, "updated_at": datetime.now(timezone.utc).isoformat()}},
        )
        return
    now_iso = datetime.now(timezone.utc).isoformat()
    await db.applications.update_one(
        {"id": application_id},
        {"$set": {
            "status": "paid",
            "payment_status": payment_status,
            "paid_at": now_iso,
            "updated_at": now_iso,
        }},
    )
    if app_doc.get("coupon_code"):
        await db.coupons.update_one(
            {"code": app_doc["coupon_code"]},
            {"$inc": {"used_count": 1}},
        )

    # Fire-and-forget emails (do not block)
    settings_doc = await db.settings.find_one({"id": "global"}, {"_id": 0}) or {}
    site_name = settings_doc.get("site_name", "Maxlife88 Agent Portal")
    updated = await db.applications.find_one({"id": application_id}, {"_id": 0}) or app_doc
    try:
        from pdf_service import build_application_pdf
        pdf_bytes = build_application_pdf(updated, site_name=site_name)
    except Exception as e:
        logger.error("PDF generation failed: %s", e)
        pdf_bytes = None
    try:
        await send_payment_received_email(
            to=app_doc["email"],
            full_name=app_doc["full_name"],
            amount=float(app_doc.get("price_amount", 0)),
            payment_id=str(app_doc.get("payment_id", "")),
            application_id=app_doc["id"],
            pdf_bytes=pdf_bytes,
        )
    except Exception as e:
        logger.error("payment_received email failed: %s", e)
    try:
        settings = await db.settings.find_one({"id": "global"}, {"_id": 0}) or {}
        notify_to = settings.get("notification_email") or os.environ.get("ADMIN_EMAIL")
        if notify_to:
            await send_admin_new_application_email(notify_to, app_doc)
    except Exception as e:
        logger.error("admin notify email failed: %s", e)


@router.post("/webhook")
async def nowpayments_webhook(request: Request, x_nowpayments_sig: Optional[str] = Header(None), db=Depends(get_db)):
    body = await request.body()
    body_text = body.decode("utf-8", errors="replace")

    if not x_nowpayments_sig:
        logger.warning("Webhook missing x-nowpayments-sig header")
        raise HTTPException(status_code=400, detail="Missing signature")

    if not await nowp.verify_ipn_signature(body_text, x_nowpayments_sig):
        logger.warning("Webhook signature verification failed")
        raise HTTPException(status_code=401, detail="Invalid signature")

    try:
        data = json.loads(body_text)
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid JSON")

    payment_id = str(data.get("payment_id") or "")
    payment_status = data.get("payment_status")
    order_id = data.get("order_id")  # = application id

    if not order_id:
        logger.warning("Webhook missing order_id (data=%s)", data)
        return {"ok": True}

    # Update payment_status regardless
    await db.applications.update_one(
        {"id": order_id},
        {"$set": {
            "payment_status": payment_status,
            "payment_id": payment_id or None,
            "updated_at": datetime.now(timezone.utc).isoformat(),
        }},
    )

    if nowp.is_paid_status(payment_status):
        await _apply_payment_success(order_id, payment_status, db)

    return {"ok": True}
