"""钱包 / 我的资产 endpoint。

路由前缀 `/api/v1/wallet`:
  GET  /account              金币 + 现金余额(我的资产卡)
  GET  /coin-transactions    金币流水(游标分页)
"""
from __future__ import annotations

import logging

from fastapi import APIRouter, Depends, HTTPException, Query, status

from app.api.deps import CurrentUser, DbSession
from app.core.config import settings
from app.core.ratelimit import rate_limit
from app.core.rewards import (
    COIN_PER_CENT,
    COIN_PER_YUAN,
    MIN_EXCHANGE_COIN,
    WITHDRAW_MAX_CENTS,
    WITHDRAW_MIN_CENTS,
)
from app.repositories import wallet as crud_wallet
from app.models.user import User
from app.schemas.welfare import (
    BindWechatRequest,
    BindWechatResultOut,
    CashTransactionOut,
    CashTransactionPage,
    CoinAccountOut,
    CoinTransactionOut,
    CoinTransactionPage,
    ExchangeInfoOut,
    ExchangeRequest,
    ExchangeResultOut,
    TransferAuthResultOut,
    TransferAuthStatusOut,
    UnbindWechatRequest,
    UnbindWechatResultOut,
    WithdrawInfoOut,
    WithdrawOrderOut,
    WithdrawOrderPage,
    WithdrawRequest,
    WithdrawResultOut,
    WithdrawStatusOut,
)

logger = logging.getLogger("shagua.wallet")

router = APIRouter(prefix="/api/v1/wallet", tags=["wallet"])


@router.get("/account", response_model=CoinAccountOut, summary="金币+现金余额")
def get_account(user: CurrentUser, db: DbSession) -> CoinAccountOut:
    acc = crud_wallet.get_or_create_account(db, user.id)
    return CoinAccountOut.model_validate(acc)


@router.get(
    "/coin-transactions",
    response_model=CoinTransactionPage,
    summary="金币流水(游标分页)",
)
def coin_transactions(
    user: CurrentUser,
    db: DbSession,
    limit: int = Query(20, ge=1, le=100),
    cursor: int | None = Query(None, description="上一页末条 id"),
) -> CoinTransactionPage:
    items, next_cursor = crud_wallet.list_coin_transactions(
        db, user.id, limit=limit, cursor=cursor
    )
    return CoinTransactionPage(
        items=[CoinTransactionOut.model_validate(it) for it in items],
        next_cursor=next_cursor,
    )


@router.get("/cash-transactions", response_model=CashTransactionPage, summary="现金流水(游标分页)")
def cash_transactions(
    user: CurrentUser,
    db: DbSession,
    limit: int = Query(20, ge=1, le=100),
    cursor: int | None = Query(None, description="上一页末条 id"),
) -> CashTransactionPage:
    items, next_cursor = crud_wallet.list_cash_transactions(
        db, user.id, limit=limit, cursor=cursor
    )
    return CashTransactionPage(
        items=[CashTransactionOut.model_validate(it) for it in items],
        next_cursor=next_cursor,
    )


@router.get("/exchange-info", response_model=ExchangeInfoOut, summary="金币兑现金汇率/规则")
def exchange_info() -> ExchangeInfoOut:
    return ExchangeInfoOut(
        coin_per_yuan=COIN_PER_YUAN,
        min_coin=MIN_EXCHANGE_COIN,
        step_coin=COIN_PER_CENT,
    )


@router.post("/exchange", response_model=ExchangeResultOut, summary="金币兑现金")
def exchange(req: ExchangeRequest, user: CurrentUser, db: DbSession) -> ExchangeResultOut:
    try:
        acc, cents = crud_wallet.exchange_coins_to_cash(db, user.id, req.coin_amount)
    except crud_wallet.InvalidExchangeAmountError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"coin_amount must be >= {MIN_EXCHANGE_COIN} and a multiple of {COIN_PER_CENT}",
        ) from e
    except crud_wallet.InsufficientCoinError as e:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="insufficient coin balance") from e

    logger.info("exchange ok user_id=%d coin=%d cents=%d", user.id, req.coin_amount, cents)
    return ExchangeResultOut(
        coin_amount=req.coin_amount,
        cash_added_cents=cents,
        coin_balance=acc.coin_balance,
        cash_balance_cents=acc.cash_balance_cents,
    )


# ===== 提现(现金 → 微信零钱) =====


@router.post(
    "/bind-wechat",
    response_model=BindWechatResultOut,
    summary="微信授权 code 换 openid 并绑定",
    dependencies=[Depends(rate_limit(10, 60, "bind-wechat"))],  # #6 同 IP 每分钟≤10 次
)
def bind_wechat(req: BindWechatRequest, user: CurrentUser, db: DbSession) -> BindWechatResultOut:
    if not settings.wxpay_configured:
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="wechat pay not configured")
    try:
        info = crud_wallet.bind_wechat_openid(db, user.id, req.code)
    except crud_wallet.WechatAlreadyBoundError as e:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT, detail="该微信已绑定其他账号"
        ) from e
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e
    logger.info(
        "bind wechat ok user_id=%d openid=%s*** nickname=%r avatar=%s",
        user.id, info["openid"][:6], info["nickname"], bool(info["avatar_url"]),
    )
    return BindWechatResultOut(
        bound=True, wechat_nickname=info["nickname"], wechat_avatar_url=info["avatar_url"]
    )


@router.post("/unbind-wechat", response_model=UnbindWechatResultOut, summary="解绑微信(清空 openid)")
def unbind_wechat(
    user: CurrentUser, db: DbSession, req: UnbindWechatRequest | None = None
) -> UnbindWechatResultOut:
    # 有「待审核(reviewing)」提现单时,首次解绑拦下要求确认:这类单还没打款,确认解绑(force)
    # 时会被立即退回现金余额(refund_reviewing_withdraws_on_unbind),后台不再挂单,用户需先知情。
    # (pending 单转账已在途、解绑影响不到,不拦;见 has_reviewing_withdraw。)
    force = req.force if req is not None else False
    if not force and crud_wallet.has_reviewing_withdraw(db, user.id):
        logger.info("unbind wechat needs_confirm(有待审核提现) user_id=%d", user.id)
        return UnbindWechatResultOut(
            bound=bool(user.wechat_openid),
            needs_confirm=True,
            message="你有提现正在审核中,解绑微信后这笔会自动退回到现金余额。确定解绑吗?",
        )
    # 先把待审核提现立即退回现金(无则 no-op),再清 openid —— 让"解绑即退"名副其实
    refunded = crud_wallet.refund_reviewing_withdraws_on_unbind(db, user.id)
    crud_wallet.unbind_wechat_openid(db, user.id)
    logger.info("unbind wechat ok user_id=%d force=%s refunded_withdraws=%d", user.id, force, refunded)
    return UnbindWechatResultOut(bound=False)


@router.get("/withdraw-info", response_model=WithdrawInfoOut, summary="提现额度/绑定状态/免确认开关")
def withdraw_info(user: CurrentUser, db: DbSession) -> WithdrawInfoOut:
    u = db.get(User, user.id)
    # 顺带同步免确认授权状态(捕获首单确认后已生效的授权 pending→active),让开关展示实时
    auth = crud_wallet.sync_transfer_auth(db, user.id)
    return WithdrawInfoOut(
        min_cents=WITHDRAW_MIN_CENTS,
        max_cents=WITHDRAW_MAX_CENTS,
        wechat_bound=bool(u and u.wechat_openid),
        wechat_nickname=u.wechat_nickname if u else None,
        wechat_avatar_url=u.wechat_avatar_url if u else None,
        transfer_auth_enabled=bool(auth and auth.state == "active"),
    )


@router.post(
    "/withdraw",
    response_model=WithdrawResultOut,
    summary="发起提现(扣款建单,待人工审核;审核通过后才打款)",
    dependencies=[Depends(rate_limit(5, 60, "withdraw"))],  # IP 级粗限流;用户级未完成单限制在仓库层
)
def withdraw(req: WithdrawRequest, user: CurrentUser, db: DbSession) -> WithdrawResultOut:
    # 提现发起本身不调微信(打款在审核通过后),但仍要求微信支付已配置——否则审核通过也打不了款,提前拦
    if not settings.wxpay_configured:
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="wechat pay not configured")
    try:
        order = crud_wallet.create_withdraw(
            db, user.id, req.amount_cents, source=req.source,
            user_name=req.user_name, out_bill_no=req.out_bill_no,
        )
    except crud_wallet.InvalidWithdrawAmountError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="提现金额不符合要求",
        ) from e
    except crud_wallet.WechatNotBoundError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请先绑定微信") from e
    except crud_wallet.WithdrawTooFrequentError as e:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="已有提现申请正在审核或打款中,请处理完成后再申请",
        ) from e
    except crud_wallet.InsufficientCashError as e:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="现金余额不足") from e

    # 调试直发(非生产):skip_review=true 时跳过人工审核,立即发起微信转账(等价 admin approve)。
    # 双闸保护——客户端仅 debug 包下发此 flag,服务端仅非 prod 才认;任一道闸拦住即恢复正常审核,
    # 生产绝不会被客户端 flag 绕过审核。用于本地联调"提现→微信转账/免确认到账"全链路。
    if req.skip_review and not settings.is_prod and order.status == "reviewing":
        logger.warning(
            "withdraw skip_review(非prod调试直发,跳过人工审核立即打款) user_id=%d bill=%s",
            user.id, order.out_bill_no,
        )
        order = crud_wallet.execute_withdraw_transfer(db, order)

    acc = crud_wallet.get_or_create_account(db, user.id)
    logger.info(
        "withdraw submitted user_id=%d cents=%d bill=%s status=%s",
        user.id, req.amount_cents, order.out_bill_no, order.status,
    )
    # 此刻 status=reviewing,尚未打款 → 无 package_info;App 据 status 提示"已提交,等待审核"。
    # 审核通过后客户端轮询 /withdraw/status 拿到 package_info(若需确认)再拉起微信确认页。
    return WithdrawResultOut(
        out_bill_no=order.out_bill_no,
        status=order.status,
        wechat_state=order.wechat_state,
        amount_cents=order.amount_cents,
        cash_balance_cents=acc.cash_balance_cents,
        package_info=order.package_info,
        mch_id=settings.WXPAY_MCH_ID,
        app_id=settings.WECHAT_APP_ID,
    )


@router.get("/withdraw/status", response_model=WithdrawStatusOut, summary="查提现单状态(轮询)")
def withdraw_status(
    user: CurrentUser, db: DbSession, out_bill_no: str = Query(..., description="商户提现单号")
) -> WithdrawStatusOut:
    try:
        # 用户从确认页返回后查单:仍 WAIT_USER_CONFIRM 视为放弃 → 撤销+退款
        order = crud_wallet.refresh_withdraw_status(
            db, user.id, out_bill_no, cancel_if_unconfirmed=True
        )
    except crud_wallet.WithdrawOrderNotFound as e:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="withdraw order not found") from e
    return WithdrawStatusOut(
        out_bill_no=order.out_bill_no,
        status=order.status,
        wechat_state=order.wechat_state,
        amount_cents=order.amount_cents,
        fail_reason=order.fail_reason,
        # 审核通过后 status=pending 且可能带 package_info(WAIT_USER_CONFIRM):供 App 拉起微信确认页
        package_info=order.package_info,
        mch_id=settings.WXPAY_MCH_ID if order.package_info else None,
        app_id=settings.WECHAT_APP_ID if order.package_info else None,
    )


@router.get("/withdraw-orders", response_model=WithdrawOrderPage, summary="提现单列表(游标分页)")
def withdraw_orders(
    user: CurrentUser,
    db: DbSession,
    source: str | None = Query(None, description="按账户来源过滤:coin_cash / invite_cash;不传=全部"),
    limit: int = Query(20, ge=1, le=100),
    cursor: int | None = Query(None, description="上一页末条 id"),
) -> WithdrawOrderPage:
    items, next_cursor = crud_wallet.list_withdraw_orders(db, user.id, source=source, limit=limit, cursor=cursor)
    return WithdrawOrderPage(
        items=[WithdrawOrderOut.model_validate(it) for it in items],
        next_cursor=next_cursor,
    )


# ===== 免确认收款授权(用户授权免确认模式)=====
# 开启一次后,后续提现走免确认转账直接到账,不再跳微信确认。绑定 openid 是前提。


@router.post(
    "/transfer-auth",
    response_model=TransferAuthResultOut,
    summary="开启免确认到账(申请授权,返回拉起微信授权页的 package)",
    dependencies=[Depends(rate_limit(10, 60, "transfer-auth"))],
)
def open_transfer_auth(user: CurrentUser, db: DbSession) -> TransferAuthResultOut:
    if not settings.wxpay_auth_configured:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="wechat transfer auth not configured",
        )
    try:
        info = crud_wallet.apply_transfer_auth(db, user.id)
    except crud_wallet.WechatNotBoundError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="wechat not bound") from e
    except crud_wallet.WithdrawTransferError as e:
        raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(e)) from e
    logger.info("open transfer-auth user_id=%d already_active=%s", user.id, info["already_active"])
    return TransferAuthResultOut(**info)


@router.get(
    "/transfer-auth/status",
    response_model=TransferAuthStatusOut,
    summary="查免确认授权状态(从微信授权页返回后轮询)",
)
def transfer_auth_status(user: CurrentUser, db: DbSession) -> TransferAuthStatusOut:
    auth = crud_wallet.sync_transfer_auth(db, user.id)
    state = auth.state if auth else "none"
    return TransferAuthStatusOut(state=state, enabled=(state == "active"))


@router.post(
    "/transfer-auth/close",
    response_model=TransferAuthStatusOut,
    summary="关闭免确认到账(解除授权)",
)
def close_transfer_auth_endpoint(user: CurrentUser, db: DbSession) -> TransferAuthStatusOut:
    crud_wallet.close_transfer_auth(db, user.id)
    logger.info("close transfer-auth user_id=%d", user.id)
    return TransferAuthStatusOut(state="closed", enabled=False)
