"""Admin 订单运费：受控重算 + 报价明细/历史。

从 orders.py 拆出来（那边已经 1200+ 行，Task 8 还要往上加）。依赖方向单向：
orders.py 从这里 import，这里不 import orders.py。

三条不变式：
1. 运费只在操作员**显式确认**后才变。编辑商品/地址只挂警告，绝不动钱。
2. 已付款/已发货/已完成/已退款订单永不重算——那笔运费是历史事实，退款按它算。
3. 只有高级运费**强制模式**的租户能重算。插件没开的租户运费可能是人工填的，
   重算会把它换成 ShippingCalculator 算的数——那正是本任务要防的破坏性改动。
"""
from __future__ import annotations

from datetime import datetime
from decimal import Decimal
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db, require_permission
from app.core.models.customer import Customer
from app.core.models.order import Order, OrderItem
from app.core.models.user import User
from app.core.services import pricing as pricing_svc
from app.core.services.checkout_facts import SHIPPING_ADDRESS_FIELDS, resolve_is_pickup
from app.services.audit import log_audit

#: 挂在 orders.py 那个 /orders 路由上（router.include_router），不单独注册前缀
router = APIRouter()

#: 允许重算运费的订单状态
_RECALC_ALLOWED_STATUSES = frozenset({"pending", "draft", "unpaid"})

#: 只留最近 20 条重算记录，够解释账目了
_HISTORY_LIMIT = 20


class ShippingRecalcBody(BaseModel):
    """重算运费。confirm=False 只出方案，不写库。

    advanced_quote 是确认时把**方案预览里那个候选**带回来的标识：不带的话服务端
    重新问价，中间规则/地址变过就会静默按新金额落账，操作员批的是另一个数。
    带上之后候选集变了就是 stale_quote，让人重新看一眼。
    """
    confirm:        bool = False
    advanced_quote: Optional[dict] = None
    delivery_mode:  Optional[str]  = None


async def load_admin_order(db: AsyncSession, tenant_id: int, order_id: int, *,
                           lock: bool = False) -> Order:
    """按租户取订单。改钱的路径必须 lock=True。

    没有行锁时两个并发的确认重算会各读到同一个 old_fee；更糟的是 extra_attributes
    是整列 JSON 写入，并发的 edit_order 会拿它读到的旧 extra 覆盖回去，
    **把刚写进去的快照和整段重算历史一起抹掉**——正是本任务要建立的审计轨迹。
    """
    stmt = select(Order).where(Order.id == order_id, Order.tenant_id == tenant_id)
    if lock:
        stmt = stmt.with_for_update()
    o = (await db.execute(stmt)).scalar_one_or_none()
    if not o:
        raise HTTPException(404, {"error": "order_not_found",
                                  "reason_keys": ["order_not_found"]})
    return o


async def shipping_recalc_blockers(db: AsyncSession, tenant_id: int, order) -> list[str]:
    """不可重算的原因（可重算时为空）。前端按 reason key 出本地化文案。"""
    if getattr(order, "paid_at", None):
        # 状态可能还停在 pending，但钱已经收了——以 paid_at 为准
        return ["order_already_paid"]
    if (order.status or "") not in _RECALC_ALLOWED_STATUSES:
        return ["order_not_editable"]
    from app.plugins.advanced_shipping_rules.store_quote import is_enforced

    if not await is_enforced(db, tenant_id):
        return ["advanced_shipping_not_enforced"]
    return []


async def assert_shipping_recalculable(db: AsyncSession, tenant_id: int, order) -> None:
    blockers = await shipping_recalc_blockers(db, tenant_id, order)
    if blockers:
        raise HTTPException(400, {"error": "shipping_recalc_not_allowed",
                                  "reason_keys": blockers})


def advanced_shipping_block(quote) -> Optional[dict]:
    """预览响应里的高级运费候选（基准币种金额 + locale 展示文案）。插件未强制时为 None。"""
    if quote.mode != "enforced":
        return None
    return {
        "mode": quote.mode,
        "pickup": quote.pickup,
        "reason_keys": list(quote.reason_keys),
        "selected": quote.snapshot,
        "quotes": [
            {
                "shipping_method_id": q.shipping_method_id,
                "quote_group": q.quote_group,
                "delivery_mode": q.delivery_mode,
                "fee": float(q.fee),
                "currency": q.currency,
                "title_map": dict(q.title_map),
                "description_map": dict(q.description_map),
            }
            for q in quote.quotes
        ],
    }


async def _quote_existing_order(db: AsyncSession, tenant_id: int, order: Order, *,
                                selection: Optional[dict] = None,
                                delivery_mode: Optional[str] = None):
    """按订单上已有的事实重新取一次报价，走的仍是 Store 那个解析器。

    行金额取订单明细里的**实际成交价**（管理员可能改过价），不回头按目录价重算：
    按目录价算出来的小计会去匹配另一条运费规则，客户会莫名其妙被换一个运费档。

    ponytail: 积分抵扣按 0 计（订单上的积分早已核销，再传一次会去校验余额），
    而优惠券**是**重新套用的——所以按 cart_total 分档的规则会系统性读高一点。
    要精确就把已用积分金额写进快照再读回来。
    """
    from app.plugins.advanced_shipping_rules.store_quote import quote_checkout_pricing

    cust_r = await db.execute(select(Customer).where(
        Customer.id == order.customer_id, Customer.tenant_id == tenant_id))
    customer = cust_r.scalar_one_or_none()

    rows = (await db.execute(
        select(OrderItem).where(OrderItem.order_id == order.id))).scalars().all()
    if not rows:
        raise HTTPException(400, {"error": "shipping_recalc_not_allowed",
                                  "reason_keys": ["order_has_no_items"]})
    cart = [
        pricing_svc.PricingCartItem(
            product_id=oi.product_id, variant_id=oi.variant_id,
            qty=oi.quantity, unit_price_override=oi.unit_price,
        )
        for oi in rows
    ]

    addr = order.shipping_address or {}
    extra = order.extra_attributes or {}
    quote = await quote_checkout_pricing(
        db, tenant_id,
        customer=customer,
        items=cart,
        address={f: str(addr.get(f) or "") for f in SHIPPING_ADDRESS_FIELDS},
        is_pickup=await resolve_is_pickup(db, tenant_id, extra.get("delivery_type")),
        coupon_code=extra.get("coupon_code"),
        points_to_use=0,
        shipping_method_id=order.shipping_method_id,
        delivery_mode=delivery_mode,
        selection=selection,
        payment_method=extra.get("pay_method"),
        require_quote=True, record_shadow=False,
    )
    return quote, rows


async def _retax(db: AsyncSession, tenant_id: int, order: Order, rows, shipping_total: Decimal):
    """按新运费重算税额，返回 (tax_total, prices_include_tax)。

    本平台的税务插件有 tax_shipping 开关（tax/calculator.py），运费**会**计税：
    只挪 grand_total 的运费差额，10 → 40 的重算会让税还停在按 10 算的数上，
    grand_total 短收 30 元的 GST——而这单马上就要收款。

    税务插件未启用时保持订单上原有的税额（与 edit_order 同一套兜底）。
    """
    lines = [
        pricing_svc.PricingLine(
            product_id=oi.product_id, variant_id=oi.variant_id,
            name=(oi.product_snapshot or {}).get("name", ""),
            sku=(oi.product_snapshot or {}).get("sku", ""),
            qty=oi.quantity, unit_price=oi.unit_price,
            line_total=oi.total_price, snapshot=oi.product_snapshot or {},
        )
        for oi in rows
    ]
    addr = order.shipping_address or {}
    try:
        from app.plugins.tax.calculator import calculate_order_tax

        result = await calculate_order_tax(
            db=db, tenant_id=tenant_id, lines=lines, shipping_total=shipping_total,
            country=addr.get("country", ""), province=addr.get("province", ""),
        )
        return result.total_tax, result.prices_include_tax
    except Exception:                                   # noqa: BLE001 — 插件未启用
        return (order.tax_total or Decimal("0")), False


@router.post("/{order_id}/recalculate-shipping", summary="重算运费（草稿/未付款，需确认）")
async def recalculate_order_shipping(
    order_id: int,
    body: ShippingRecalcBody,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(require_permission("orders.update")),
):
    """显式重算运费。confirm=False 只返回方案，confirm=True 才替换运费与快照。"""
    tid = current_user.tenant_id
    o = await load_admin_order(db, tid, order_id, lock=body.confirm)
    await assert_shipping_recalculable(db, tid, o)
    if (body.confirm and not body.advanced_quote
            and not await resolve_is_pickup(db, tid, (o.extra_attributes or {}).get("delivery_type"))):
        raise HTTPException(400, {"error": "advanced_shipping_quote_required",
                                  "reason_keys": ["quote_selection_required"]})

    quote, rows = await _quote_existing_order(db, tid, o, selection=body.advanced_quote,
                                              delivery_mode=body.delivery_mode)
    old_fee = o.shipping_total or Decimal("0")
    new_fee = quote.pricing.shipping_total

    if not body.confirm:
        return {
            "ok": True, "applied": False,
            "current_shipping_total": float(old_fee),
            "proposal": {
                "shipping_total": float(new_fee),
                "snapshot": quote.snapshot,
                "advanced_shipping": advanced_shipping_block(quote),
            },
        }

    actor_name = (current_user.profile.get("name") if isinstance(current_user.profile, dict)
                  else (current_user.profile if isinstance(current_user.profile, str) else None))
    tax_total, prices_include_tax = await _retax(db, tid, o, rows, new_fee)

    o.shipping_total = new_fee
    o.tax_total = tax_total
    # 由各组成部分重算，而不是 grand_total ± 差额：差额写法在并发/重试下会把
    # 同一笔差额加两次，总额越算越偏；这个写法重复执行结果不变。
    subtotal = o.subtotal or Decimal("0")
    discount = o.discount_total or Decimal("0")
    o.grand_total = max(Decimal("0"), subtotal - discount + new_fee
                        + (Decimal("0") if prices_include_tax else tax_total))

    extra = {**(o.extra_attributes or {})}
    extra.pop("advanced_shipping_stale", None)
    if quote.snapshot:
        extra["advanced_shipping_quote"] = quote.snapshot
    history = list(extra.get("advanced_shipping_history") or [])
    history.append({
        "at": datetime.now().isoformat(timespec="seconds"),
        "actor_id": current_user.id,
        "actor_name": actor_name,
        "from": f"{old_fee:.2f}",
        "to": f"{new_fee:.2f}",
        "snapshot": quote.snapshot,
    })
    extra["advanced_shipping_history"] = history[-_HISTORY_LIMIT:]
    o.extra_attributes = extra

    await db.commit()
    await log_audit(
        db=db, tenant_id=tid, action="orders.recalculate_shipping",
        actor_type="admin", actor_id=current_user.id, actor_name=actor_name,
        target_type="orders", target_id=order_id, target_name=o.order_no,
        changes={"shipping_total": [f"{old_fee:.2f}", f"{new_fee:.2f}"]},
    )
    return {
        "ok": True, "applied": True,
        "shipping_total": float(new_fee),
        "tax_total": float(tax_total),
        "grand_total": float(o.grand_total),
        "snapshot": quote.snapshot,
        "advanced_shipping": advanced_shipping_block(quote),
    }


@router.get("/{order_id}/shipping-quote", summary="订单运费报价明细与重算历史")
async def get_order_shipping_quote(
    order_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(require_permission("orders.detail.view")),
):
    """只读：当前实收运费、报价快照、过期警告、历次重算记录。

    退款依据的就是这里的 shipping_total 与快照，不会临时向引擎重新问价。
    """
    o = await load_admin_order(db, current_user.tenant_id, order_id)
    extra = o.extra_attributes or {}
    reason_keys = await shipping_recalc_blockers(db, current_user.tenant_id, o)
    return {
        "shipping_total": float(o.shipping_total or 0),
        "snapshot": extra.get("advanced_shipping_quote"),
        "stale": extra.get("advanced_shipping_stale"),
        "history": extra.get("advanced_shipping_history") or [],
        "recalculable": not reason_keys,
        "reason_keys": reason_keys,
    }


__all__ = [
    "ShippingRecalcBody", "advanced_shipping_block", "assert_shipping_recalculable",
    "get_order_shipping_quote", "load_admin_order", "recalculate_order_shipping", "router",
    "shipping_recalc_blockers",
]
