"""POS 退货的纯计算逻辑：退款拆分与退货期限。

刻意不依赖数据库，便于直接测试——这里算错就是真金白银出错。
"""
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timedelta
from decimal import Decimal


class ReturnInvalid(ValueError):
    """退货请求不合法（超出可退金额、超出退货期限等）。"""


# EFTPOS 必须排在 CASH 之前：只有终端明确退款成功后才允许交付现金。
_TENDER_ORDER = {"eftpos": 0, "cash": 1}


@dataclass(frozen=True)
class TenderSlice:
    paymentMethod: str
    amountCents: int
    paymentId: int | None = None
    originalTxnRef: str | None = None


@dataclass(frozen=True)
class TenderAvailable:
    """原订单某笔支付的可退余额。"""
    paymentMethod: str
    paidCents: int
    refundedCents: int = 0
    paymentId: int | None = None
    originalTxnRef: str | None = None

    @property
    def availableCents(self) -> int:
        return max(self.paidCents - self.refundedCents, 0)


def split_refund(tenders: list[TenderAvailable], refund_cents: int) -> list[TenderSlice]:
    """把退款金额按 EFTPOS 优先、现金兜底的顺序分配到各支付方式。

    - 优先吃满 EFTPOS 可退余额，不够再用 CASH 补齐，减少终端交互次数
    - 每笔不得超过该支付方式尚未退款的余额
    - 返回顺序为 EFTPOS 在前，CASH 在后
    """
    if refund_cents < 0:
        raise ReturnInvalid("退款金额不能为负")
    if refund_cents == 0:
        return []

    usable = sorted(
        [t for t in tenders if t.availableCents > 0],
        key=lambda t: _TENDER_ORDER.get(t.paymentMethod, 99),
    )
    total_available = sum(t.availableCents for t in usable)
    if refund_cents > total_available:
        raise ReturnInvalid(f"退款金额超出可退余额（可退 {total_available} 分）")

    # ponytail: 贪心分配，EFTPOS 先吃满再轮到 CASH
    remaining = refund_cents
    slices = []
    for t in usable:
        take = min(remaining, t.availableCents)
        if take > 0:
            slices.append(TenderSlice(
                paymentMethod=t.paymentMethod, amountCents=take,
                paymentId=t.paymentId, originalTxnRef=t.originalTxnRef,
            ))
            remaining -= take
        if remaining <= 0:
            break
    return slices


def within_return_window(order_paid_at: datetime, now: datetime, window_days: int) -> bool:
    """退货期限以原订单完成时间计算；window_days <= 0 表示不限期。"""
    if window_days <= 0:
        return True
    return now <= order_paid_at + timedelta(days=window_days)


def returnable_quantity(sold_qty: Decimal, already_returned: Decimal) -> Decimal:
    return max(sold_qty - already_returned, Decimal("0"))


def validate_return_quantity(
    requested: Decimal, sold_qty: Decimal, already_returned: Decimal, *, sold_by_weight: bool,
) -> Decimal:
    """校验退货数量：按件必须整数，称重最多 3 位小数。"""
    if requested <= 0:
        raise ReturnInvalid("退货数量必须大于 0")
    if not sold_by_weight and requested != requested.to_integral_value():
        raise ReturnInvalid("按件商品的退货数量必须为整数")
    if sold_by_weight and -requested.as_tuple().exponent > 3:
        raise ReturnInvalid("称重商品的退货数量最多 3 位小数")

    remaining = returnable_quantity(sold_qty, already_returned)
    if requested > remaining:
        raise ReturnInvalid(f"退货数量超出剩余可退数量（剩余 {remaining}）")
    return requested
