"""充值路由。

接口列表：
  POST /recharge                        — 创建充值订单，调易支付 mapi，返回支付 URL
  POST /recharge/notify                 — 易支付异步回调（无需登录，验签后处理）
  GET  /recharge/status/{out_trade_no}  — 前端查询订单状态（进页面时调一次即可）
  GET  /recharge/orders                 — 当前用户充值记录列表

并发保障：
  - 回调幂等锁：key=qycw:recharge:lock:{out_trade_no}，NX+EX=30s
  - 已处理标记：key=qycw:recharge:done:{out_trade_no}，EX=7天
  核心到账逻辑统一在 recharge_service.process_paid，调度器和回调共用。
"""
import json
import logging
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Request, Query
from fastapi.responses import PlainTextResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import settings
from app.core.deps import get_current_user
from app.database import get_db
from app.models.recharge_order import RechargeChannel, RechargeOrder, RechargeStatus
from app.models.user import User
from app.redis_client import k, redis_client
from app.schemas.common import ApiResponse
from app.schemas.recharge import (
    RechargeCreateRequest,
    RechargeCreateResponse,
    RechargeOrderItem,
    RechargePendingPayment,
    RechargePaymentOption,
    RechargeQueryResult,
)
from app.services import payment_service, system_setting_service
from app.services.recharge_service import clear_pending_payment, process_paid
from mode.payment.base import PaymentCreateRequest, PaymentProviderError

logger = logging.getLogger(__name__)
router = APIRouter()


def _lock_key(out_trade_no: str) -> str:
    return k("recharge", "lock", out_trade_no)


def _done_key(out_trade_no: str) -> str:
    return k("recharge", "done", out_trade_no)


def _pending_key(user_id: int) -> str:
    return k("recharge", "pending", str(user_id))


def _client_ip(request: Request) -> str:
    client_ip = request.headers.get("X-Forwarded-For", "")
    if not client_ip:
        client_ip = request.client.host if request.client else "127.0.0.1"
    return client_ip.split(",")[0].strip()


def _channel_from_method(method: str) -> RechargeChannel:
    try:
        return RechargeChannel(method)
    except ValueError:
        return RechargeChannel.ALIPAY


async def _load_recharge_order(db: AsyncSession, out_trade_no: str) -> RechargeOrder | None:
    res = await db.execute(select(RechargeOrder).where(RechargeOrder.out_trade_no == out_trade_no))
    return res.scalar_one_or_none()


def _notify_token_ok(order: RechargeOrder, provided_token: str | None) -> bool:
    if not order.notify_token:
        return True
    return provided_token == order.notify_token


# ── 创建充值订单 ──────────────────────────────────────────────────────────────


@router.get("/payment-options", response_model=ApiResponse)
async def list_payment_options(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    _ = current_user
    options = await payment_service.enabled_payment_plugins(db)
    return ApiResponse(data=[RechargePaymentOption(**item) for item in options])


@router.post("", response_model=ApiResponse)
async def create_recharge(
    body: RechargeCreateRequest,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    business_settings = await system_setting_service.get_business_settings(db)
    if not business_settings.recharge_enabled:
        raise HTTPException(status_code=403, detail="当前暂停余额充值，请稍后再试")

    # 实名认证检查
    if settings.BAIDU_FACE_VERIFY_REQUIRED and settings.BAIDU_FACE_VERIFY_FOR_RECHARGE:
        from app.services.verification_service import require_verified
        require_verified(current_user)

    amount_yuan = payment_service.format_amount(body.amount)
    amount_cents = payment_service.yuan_to_cents(body.amount)
    if amount_cents < business_settings.recharge_min_cents:
        raise HTTPException(
            status_code=400,
            detail=f"最低充值金额为 ¥{business_settings.recharge_min_cents / 100:.2f}",
        )
    if amount_cents > business_settings.recharge_max_cents:
        raise HTTPException(
            status_code=400,
            detail=f"最高充值金额为 ¥{business_settings.recharge_max_cents / 100:.2f}",
        )
    provider, plugin, payment_config = await payment_service.get_enabled_payment_provider(db, body.payment_plugin)
    plugin_id = str(plugin["id"])
    payment_method = str(getattr(provider, "method", payment_service.payment_method_from_plugin(plugin)))
    payment_name = str(plugin.get("name") or getattr(provider, "display_name", plugin_id))

    out_trade_no = payment_service.gen_out_trade_no()
    notify_token = payment_service.gen_notify_token()

    client_ip = _client_ip(request)
    request_base_url = str(request.base_url).rstrip("/")
    notify_url = payment_service.build_notify_url(
        config=payment_config,
        plugin_id=plugin_id,
        token=notify_token,
        backend_url=business_settings.backend_url,
        request_base_url=request_base_url,
    )
    return_url = payment_service.build_return_url(config=payment_config, frontend_url=business_settings.frontend_url)

    # 先落库，再调支付通道。防止 mapi 成功但写库失败导致订单丢失。
    order = RechargeOrder(
        user_id=current_user.id,
        out_trade_no=out_trade_no,
        channel=_channel_from_method(payment_method),
        payment_plugin=plugin_id,
        payment_method=payment_method,
        notify_token=notify_token,
        amount=body.amount,
        amount_cents=amount_cents,
        status=RechargeStatus.PENDING,
        client_ip=client_ip,
    )
    db.add(order)
    await db.flush()

    try:
        pay_result = await provider.create_payment(
            PaymentCreateRequest(
                out_trade_no=out_trade_no,
                amount_yuan=amount_yuan,
                subject=f"{business_settings.site_name}余额充值",
                client_ip=client_ip,
                notify_url=notify_url,
                return_url=return_url,
                user_id=current_user.id,
            )
        )
    except PaymentProviderError as e:
        logger.warning("payment create failed: plugin=%s order=%s error=%s", plugin_id, out_trade_no, e.message)
        raise HTTPException(status_code=e.status_code, detail=e.message)
    except Exception as e:
        logger.exception("payment create unexpected error: plugin=%s order=%s error=%s", plugin_id, out_trade_no, e)
        raise HTTPException(status_code=502, detail="支付通道暂时不可用，请稍后重试")

    # 回填支付平台订单号
    order.trade_no = pay_result.platform_trade_no
    await db.flush()

    pending_ttl_seconds = max(60, int(business_settings.pending_payment_ttl_minutes) * 60)
    expires_at = datetime.now(timezone.utc) + timedelta(seconds=pending_ttl_seconds)
    pending_payload = {
        "out_trade_no": out_trade_no,
        "amount": str(body.amount),
        "payment_plugin": plugin_id,
        "payment_method": payment_method,
        "payment_name": payment_name,
        "payurl": pay_result.pay_url,
        "qrcode": pay_result.qr_code,
        "urlscheme": pay_result.url_scheme,
        "trade_no": pay_result.platform_trade_no,
        "expires_at": expires_at.isoformat(),
    }
    try:
        await redis_client.set(
            _pending_key(current_user.id),
            json.dumps(pending_payload, ensure_ascii=False, default=str),
            ex=pending_ttl_seconds,
        )
    except Exception as e:
        logger.warning(
            "cache pending recharge failed: user=%s order=%s error=%s",
            current_user.id,
            out_trade_no,
            e,
        )

    return ApiResponse(
        data=RechargeCreateResponse(
            out_trade_no=out_trade_no,
            amount=body.amount,
            payment_plugin=plugin_id,
            payment_method=payment_method,
            payment_name=payment_name,
            payurl=pay_result.pay_url,
            qrcode=pay_result.qr_code,
            urlscheme=pay_result.url_scheme,
            trade_no=pay_result.platform_trade_no,
        )
    )


# ── 当前用户待支付充值 ───────────────────────────────────────────────────────

@router.get("/pending", response_model=ApiResponse)
async def get_pending_recharge(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    raw = await redis_client.get(_pending_key(current_user.id))
    if not raw:
        return ApiResponse(data=None)

    try:
        data = json.loads(raw)
    except Exception:
        await redis_client.delete(_pending_key(current_user.id))
        return ApiResponse(data=None)

    out_trade_no = str(data.get("out_trade_no") or "")
    if not out_trade_no:
        await redis_client.delete(_pending_key(current_user.id))
        return ApiResponse(data=None)

    res = await db.execute(
        select(RechargeOrder).where(
            RechargeOrder.out_trade_no == out_trade_no,
            RechargeOrder.user_id == current_user.id,
        )
    )
    order = res.scalar_one_or_none()
    if not order or order.status != RechargeStatus.PENDING:
        await clear_pending_payment(current_user.id, out_trade_no)
        return ApiResponse(data=None)

    return ApiResponse(
        data=RechargePendingPayment(
            out_trade_no=out_trade_no,
            amount=Decimal(str(data.get("amount", order.amount))),
            payment_plugin=data.get("payment_plugin") or order.payment_plugin,
            payment_method=data.get("payment_method") or order.payment_method,
            payment_name=data.get("payment_name"),
            payurl=data.get("payurl"),
            qrcode=data.get("qrcode"),
            urlscheme=data.get("urlscheme"),
            trade_no=data.get("trade_no"),
            expires_at=datetime.fromisoformat(str(data.get("expires_at"))),
        )
    )


# ── 支付异步回调 ──────────────────────────────────────────────────────────────

@router.post("/notify/{plugin_id}")
@router.get("/notify/{plugin_id}")
@router.post("/notify")
@router.get("/notify")
async def epay_notify(
    request: Request,
    plugin_id: str | None = None,
    db: AsyncSession = Depends(get_db),
):
    if request.method == "POST":
        form = await request.form()
        params = dict(form)
    else:
        params = dict(request.query_params)

    out_trade_no = params.get("out_trade_no", "")
    if not out_trade_no:
        return PlainTextResponse("fail")

    order = await _load_recharge_order(db, str(out_trade_no))
    if not order:
        logger.warning("payment notify order not found: %s", out_trade_no)
        return PlainTextResponse("fail")

    selected_plugin_id = plugin_id or order.payment_plugin or "epay_alipay"
    try:
        provider, _plugin, _config = await payment_service.get_payment_provider(db, selected_plugin_id)
        notify_result = await provider.parse_notify(params)
    except PaymentProviderError as exc:
        logger.warning("payment notify rejected: plugin=%s order=%s error=%s", selected_plugin_id, out_trade_no, exc.message)
        return PlainTextResponse("fail")
    except Exception as exc:
        logger.exception("payment notify unexpected error: plugin=%s order=%s error=%s", selected_plugin_id, out_trade_no, exc)
        return PlainTextResponse("fail")

    if notify_result.out_trade_no != order.out_trade_no:
        logger.warning("payment notify order mismatch: expected=%s got=%s", order.out_trade_no, notify_result.out_trade_no)
        return PlainTextResponse("fail")

    if not _notify_token_ok(order, request.query_params.get("token")):
        logger.warning("payment notify token mismatch: order=%s plugin=%s", out_trade_no, selected_plugin_id)
        return PlainTextResponse("fail")

    # 只处理成功状态；非成功状态已验签，直接确认，避免支付平台反复通知。
    if not notify_result.paid:
        return PlainTextResponse("success")

    # 快速幂等检查（无锁）
    if await redis_client.exists(_done_key(out_trade_no)):
        return PlainTextResponse("success")

    # 分布式锁，防并发重复到账
    lock_key = _lock_key(out_trade_no)
    acquired = await redis_client.set(lock_key, "1", nx=True, ex=30)
    if not acquired:
        return PlainTextResponse("success")

    try:
        ok = await process_paid(
            out_trade_no=out_trade_no,
            trade_no=notify_result.trade_no,
            money=notify_result.amount_yuan,
            db=db,
            source="callback",
            raw_params=notify_result.raw,
            expected_payment_plugin=selected_plugin_id,
            expected_payment_method=str(getattr(provider, "method", "") or order.payment_method or ""),
        )
        return PlainTextResponse("success" if ok else "fail")
    except Exception as e:
        logger.exception("epay notify process error: %s", e)
        return PlainTextResponse("fail")
    finally:
        await redis_client.delete(lock_key)


# ── 查询订单状态（前端进页面时调一次）────────────────────────────────────────

@router.get("/status/{out_trade_no}", response_model=ApiResponse)
async def get_recharge_status(
    out_trade_no: str,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """
    查询单笔订单状态。
    若本地仍是 pending，主动调易支付查询一次并触发到账（补偿回调未到的情况）。
    前端只需进财务页时调一次，不需要持续轮询——后端调度器每分钟会自动补单。
    """
    res = await db.execute(
        select(RechargeOrder).where(
            RechargeOrder.out_trade_no == out_trade_no,
            RechargeOrder.user_id == current_user.id,
        )
    )
    order = res.scalar_one_or_none()
    if not order:
        raise HTTPException(status_code=404, detail="订单不存在")

    # 已是终态，直接返回
    if order.status != RechargeStatus.PENDING:
        await clear_pending_payment(current_user.id, out_trade_no)
        return ApiResponse(
            data=RechargeQueryResult(
                out_trade_no=out_trade_no,
                status=order.status.value,
                paid=order.status == RechargeStatus.PAID,
                balance=current_user.balance if order.status == RechargeStatus.PAID else None,
            )
        )

    # 主动向订单绑定的支付插件查询一次（补偿回调未到的情况）
    try:
        selected_plugin_id = order.payment_plugin or "epay_alipay"
        provider, _plugin, _config = await payment_service.get_payment_provider(db, selected_plugin_id)
        query_result = await provider.query_order(out_trade_no)
        if query_result.paid:
            lock_key = _lock_key(out_trade_no)
            acquired = await redis_client.set(lock_key, "1", nx=True, ex=30)
            if acquired:
                try:
                    await process_paid(
                        out_trade_no=out_trade_no,
                        trade_no=query_result.trade_no,
                        money=query_result.amount_yuan,
                        db=db,
                        source="status_query",
                        raw_params=query_result.raw,
                        expected_payment_plugin=selected_plugin_id,
                        expected_payment_method=str(getattr(provider, "method", "") or order.payment_method or ""),
                    )
                finally:
                    await redis_client.delete(lock_key)
            await db.refresh(order)
            await db.refresh(current_user)
    except Exception as e:
        logger.warning("payment query_order error: %s", e)

    if order.status != RechargeStatus.PENDING:
        await clear_pending_payment(current_user.id, out_trade_no)

    return ApiResponse(
        data=RechargeQueryResult(
            out_trade_no=out_trade_no,
            status=order.status.value,
            paid=order.status == RechargeStatus.PAID,
            balance=current_user.balance if order.status == RechargeStatus.PAID else None,
        )
    )


# ── 充值记录列表 ──────────────────────────────────────────────────────────────

@router.get("/orders", response_model=ApiResponse)
async def list_recharge_orders(
    limit: int = Query(20, ge=1, le=100),
    offset: int = Query(0, ge=0),
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    res = await db.execute(
        select(RechargeOrder)
        .where(RechargeOrder.user_id == current_user.id)
        .order_by(RechargeOrder.created_at.desc())
        .limit(limit)
        .offset(offset)
    )
    orders = res.scalars().all()
    return ApiResponse(data=[RechargeOrderItem.model_validate(o) for o in orders])
