"""充值到账核心逻辑 — 供回调路由和定时补单调度器共用。"""
import json
import logging
from decimal import Decimal

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.balance_log import BalanceChangeType, BalanceLog
from app.models.recharge_order import RechargeOrder, RechargeStatus
from app.models.user import User
from app.redis_client import k, redis_client

log = logging.getLogger("qycw.recharge")


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


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


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


async def clear_pending_payment(user_id: int, out_trade_no: str) -> None:
    """Clear cached pending payment if it still points to the same order."""
    key = _pending_key(user_id)
    raw = await redis_client.get(key)
    if not raw:
        return
    try:
        data = json.loads(raw)
    except Exception:
        await redis_client.delete(key)
        return
    if data.get("out_trade_no") == out_trade_no:
        await redis_client.delete(key)


async def process_paid(
    out_trade_no: str,
    trade_no: str,
    money: Decimal,
    db: AsyncSession,
    source: str = "callback",
    raw_params: dict | None = None,
    expected_payment_plugin: str | None = None,
    expected_payment_method: str | None = None,
) -> bool:
    """
    核心到账逻辑，调用前必须已持有 Redis 分布式锁。

    参数：
      out_trade_no — 商户订单号
      trade_no     — 易支付平台订单号
      money        — 易支付返回的实付金额（Decimal，元）
      db           — AsyncSession；本函数会在到账成功时自行 commit，
                     调用方不要再用 db.begin() 包裹本调用。
      source       — 来源标记（callback / scheduler / status_query）
      raw_params   — 回调原始参数，完整保存到 callback_raw

    返回 True 表示处理成功（含幂等跳过），False 表示数据异常需告警。
    """
    done_key = _done_key(out_trade_no)

    # double-check 幂等
    done_exists = await redis_client.exists(done_key)

    # 查订单（行锁）
    res = await db.execute(
        select(RechargeOrder)
        .where(RechargeOrder.out_trade_no == out_trade_no)
        .with_for_update()
    )
    order = res.scalar_one_or_none()
    if not order:
        log.error("[%s] order not found: %s", source, out_trade_no)
        return False

    if order.status == RechargeStatus.PAID:
        await redis_client.set(done_key, "1", ex=7 * 86400)
        return True

    if order.status not in (RechargeStatus.PENDING, RechargeStatus.EXPIRED):
        log.error(
            "[%s] paid callback for non-payable order: out_trade_no=%s status=%s",
            source, out_trade_no, order.status,
        )
        return False

    if done_exists:
        log.warning(
            "[%s] stale done key ignored for unpaid order: out_trade_no=%s status=%s",
            source, out_trade_no, order.status,
        )

    if order.status == RechargeStatus.EXPIRED:
        log.warning(
            "[%s] late paid recharge will be credited: out_trade_no=%s",
            source, out_trade_no,
        )

    if expected_payment_plugin and order.payment_plugin and order.payment_plugin != expected_payment_plugin:
        log.error(
            "[%s] payment plugin mismatch: out_trade_no=%s expected=%s got=%s",
            source, out_trade_no, expected_payment_plugin, order.payment_plugin,
        )
        return False

    if expected_payment_method and order.payment_method and order.payment_method != expected_payment_method:
        log.error(
            "[%s] payment method mismatch: out_trade_no=%s expected=%s got=%s",
            source, out_trade_no, expected_payment_method, order.payment_method,
        )
        return False

    # 金额校验（Decimal 精确比对，防篡改）
    order_amount = Decimal(str(order.amount))
    if abs(money - order_amount) > Decimal("0.001"):
        log.error(
            "[%s] amount mismatch: out_trade_no=%s expected=%s got=%s",
            source, out_trade_no, order_amount, money,
        )
        return False

    # 先取用户（行锁）。前置校验全部通过后再改写订单状态，
    # 避免任何提前 return 时留下「订单已标记 PAID 但余额未到账」的脏数据。
    user_res = await db.execute(
        select(User).where(User.id == order.user_id).with_for_update()
    )
    user = user_res.scalar_one_or_none()
    if not user:
        log.error("[%s] user not found: user_id=%s", source, order.user_id)
        return False

    # 更新订单状态，保存完整回调参数
    order.status = RechargeStatus.PAID
    order.trade_no = trade_no or order.trade_no
    if expected_payment_plugin and not order.payment_plugin:
        order.payment_plugin = expected_payment_plugin
    if expected_payment_method and not order.payment_method:
        order.payment_method = expected_payment_method
    raw = raw_params or {"source": source, "trade_no": trade_no, "money": str(money)}
    order.callback_raw = json.dumps(raw, ensure_ascii=False, default=str)[:2048]

    # 给用户加余额
    user.balance += order.amount_cents
    balance_after = user.balance

    # 写余额流水
    balance_log = BalanceLog(
        user_id=user.id,
        change_type=BalanceChangeType.RECHARGE,
        delta=order.amount_cents,
        balance_after=balance_after,
        ref_no=out_trade_no,
        remark=f"充值到账 ¥{order.amount}（{source}）",
    )
    db.add(balance_log)

    # 站内信：充值到账
    from app.services import notification_service

    await notification_service.safe_notify(
        db,
        user_id=user.id,
        category="finance",
        title="充值到账",
        content=f"您的充值 ¥{order.amount} 已到账，当前余额 ¥{balance_after / 100:.2f}。",
        link="/finance",
    )

    # 关键修复：先提交事务，确保订单状态、余额、流水真正落库，再写 Redis 幂等标记。
    # 若像之前那样先写 done_key 再由外层 commit，一旦 commit 失败（数据库瞬断、
    # 死锁、连接超时等），数据库会回滚但 done_key 仍存活 7 天，后续易支付重试回调
    # 和补单调度器都会命中 done_key 而直接跳过，造成用户已付款却永久不到账的资损。
    await db.commit()

    # 落库成功后才标记幂等 key（7天）
    await redis_client.set(done_key, "1", ex=7 * 86400)
    await clear_pending_payment(user.id, out_trade_no)

    log.info(
        "[%s] paid: user=%s out_trade_no=%s amount_cents=%s balance_after=%s",
        source, user.id, out_trade_no, order.amount_cents, balance_after,
    )
    return True


async def mark_expired(out_trade_no: str, db: AsyncSession) -> None:
    """将超时未支付的订单标记为 expired。"""
    res = await db.execute(
        select(RechargeOrder)
        .where(RechargeOrder.out_trade_no == out_trade_no)
        .with_for_update()
    )
    order = res.scalar_one_or_none()
    if order and order.status == RechargeStatus.PENDING:
        order.status = RechargeStatus.EXPIRED
        await db.flush()
        await clear_pending_payment(order.user_id, out_trade_no)
        log.info("order expired: %s", out_trade_no)
