from fastapi import HTTPException
from sqlalchemy import select

from app.api.routers.orders_shipping import load_admin_order
from app.core.models.payment import Payment
from app.core.models.refund import RefundRequest
from app.plugins.advanced_order_editor.schemas import AdvancedAdjustBody, AdvancedAdjustOut


def _default_topup_gateway(order) -> str:
    """补付走 Store 现有支付入口：沿用原单 pay_method，缺省 stripe。"""
    return (order.extra_attributes or {}).get("pay_method") or "stripe"


async def invalidate_pending_adjust_payments(db, tenant_id: int, order_id: int) -> int:
    """把该订单旧的 pending 差额补款单标 invalidated，返回失效行数。

    防重复发起（spec §防重复关键）：gateway_ref 唯一约束只防同 key 重放，
    不同 idempotency_key 重复结算要靠先把旧 pending 单失效掉。
    """
    res = await db.execute(
        select(Payment).where(
            Payment.tenant_id == tenant_id,
            Payment.gateway_ref.like(f"adj:{order_id}:%"),
            Payment.status == "pending",
        ).with_for_update()
    )
    rows = res.scalars().all()
    for p in rows:
        p.status = "invalidated"
    await db.flush()
    return len(rows)


async def _find_existing_topup_payment(db, tenant_id: int, order_id: int, idempotency_key: str):
    """同 key 重放返回原记录（spec §差额结算）。invalidated 的旧单不视为命中——
    重放同名 key 会撞 gateway_ref 唯一约束，由 DB 层兜底。

    ponytail: 按 tenant_id 强制隔离跨租户同 gateway_ref（gateway_ref 唯一约束
    是全库级，别租户的同 key 会让本租户被误命中）。
    """
    res = await db.execute(
        select(Payment).where(
            Payment.tenant_id == tenant_id,
            Payment.gateway_ref == f"adj:{order_id}:{idempotency_key}",
            Payment.status != "invalidated",
        )
    )
    return res.scalars().first()


async def apply_advanced_adjust(
    db,
    tenant_id: int,
    order_id: int,
    body: AdvancedAdjustBody,
    actor_id: int,
    actor_name: str | None,
) -> AdvancedAdjustOut:
    """POST /advanced-adjust 业务逻辑：差额补款（topup）/ 差额退款（refund）。

    两种模式都先拒绝"存在未完成退款申请"的订单（spec 守卫），否则应结余额
    会把在途退款也算进去、重复发起补款或退款。

    ponytail: 本函数在事务末尾 commit（P0-1 修复），让 Payment / RefundRequest
    真正落库——get_db 不自动 commit，response 已发 200 + payment_id 但
    session 关闭时事务回滚 = 客户看到单号但表里空着。Router 层不再 commit。
    """
    order = await load_admin_order(db, tenant_id, order_id, lock=True)
    in_flight = (await db.execute(
        select(RefundRequest.id).where(
            RefundRequest.tenant_id == tenant_id,
            RefundRequest.order_id == order.id,
            RefundRequest.status.notin_(["rejected"]),
        ).limit(1)
    )).scalar_one_or_none()
    if in_flight is not None:
        raise HTTPException(400, "存在未完成退款申请，禁止结算")

    if body.mode == "topup":
        existing = await _find_existing_topup_payment(db, tenant_id, order.id, body.idempotency_key)
        if existing is not None:
            # ponytail: 重放路径什么都没写，但 load_admin_order(lock=True) 已经
            # 拿了行锁。显式 rollback 当场释放，别让它一直持到 get_db 关闭 session
            # ——同一订单的并发改单会被这段纯读路径白白阻塞。
            await db.rollback()
            return AdvancedAdjustOut(
                payment_id=existing.id, amount=existing.amount,
                mode="topup", status=existing.status,
            )
        await invalidate_pending_adjust_payments(db, tenant_id, order.id)
        payment = Payment(
            tenant_id=tenant_id, order_id=order.id,
            gateway=_default_topup_gateway(order),
            gateway_ref=f"adj:{order.id}:{body.idempotency_key}",
            amount=body.amount, currency=order.currency, status="pending",
            extra_data={"source": "advanced_editor", "actor_id": actor_id, "note": body.note},
        )
        db.add(payment)
        await db.flush()
        await db.commit()
        return AdvancedAdjustOut(
            payment_id=payment.id, amount=payment.amount,
            mode="topup", status="pending",
        )

    rr = RefundRequest(
        tenant_id=tenant_id, order_id=order.id, order_no=order.order_no,
        customer_id=order.customer_id, type="refund",
        reason=f"advanced_editor:{body.idempotency_key}",
        refund_amount=body.amount, status="pending",
        admin_note=body.note, admin_id=actor_id,
    )
    db.add(rr)
    await db.flush()
    await db.commit()
    return AdvancedAdjustOut(
        refund_request_id=rr.id, amount=rr.refund_amount,
        mode="refund", status="pending",
    )
