"""成本计算：移动加权平均 / FIFO / 批次实际。纯逻辑。

出库成本一旦写入不再因后续进价变化而改写，保证历史利润稳定。
"""
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP

_Q = Decimal("0.0001")


def _round(x: Decimal) -> Decimal:
    return x.quantize(_Q, rounding=ROUND_HALF_UP)


def moving_average(prev_qty: Decimal, prev_avg: Decimal,
                   in_qty: Decimal, in_cost: Decimal) -> Decimal:
    """入库后的新移动加权单位成本。"""
    if in_qty <= 0:
        raise ValueError("入库数量必须大于 0")
    total_qty = prev_qty + in_qty
    if total_qty <= 0:
        return _round(in_cost)
    total_val = prev_qty * prev_avg + in_qty * in_cost
    return _round(total_val / total_qty)


@dataclass
class Layer:
    qty_remaining: Decimal
    unit_cost: Decimal


def consume_fifo(layers: list[Layer], out_qty: Decimal) -> tuple[Decimal, list[Layer]]:
    """按 FIFO 消耗成本层，返回 (出库总成本, 更新后的层)。

    layers 按入库先后排列（旧在前）。库存不足时按已有层计成本，剩余按最后一层单价
    （无层则 0）——负库存的成本用最近单价兜底，避免出库无成本。
    """
    if out_qty <= 0:
        raise ValueError("出库数量必须大于 0")
    remaining = out_qty
    cost = Decimal("0")
    new_layers = [Layer(l.qty_remaining, l.unit_cost) for l in layers]
    for layer in new_layers:
        if remaining <= 0:
            break
        take = layer.qty_remaining if layer.qty_remaining < remaining else remaining
        if take <= 0:
            continue
        cost += take * layer.unit_cost
        layer.qty_remaining -= take
        remaining -= take
    if remaining > 0:
        fallback = new_layers[-1].unit_cost if new_layers else Decimal("0")
        cost += remaining * fallback
    return _round(cost), [l for l in new_layers if l.qty_remaining > 0]


def batch_actual_cost(unit_cost: Decimal, out_qty: Decimal) -> Decimal:
    """批次实际成本：直接用该批次入库单价。"""
    if out_qty <= 0:
        raise ValueError("出库数量必须大于 0")
    return _round(unit_cost * out_qty)


def allocate_landed_cost(total_cost: Decimal, bases: list[Decimal]) -> list[Decimal]:
    """Allocate a cost exactly; the last non-zero basis absorbs rounding residue."""
    total = Decimal(str(total_cost)).quantize(_Q)
    weights = [max(Decimal("0"), Decimal(str(value))) for value in bases]
    denominator = sum(weights, Decimal("0"))
    if total < 0:
        raise ValueError("total_cost must be non-negative")
    if denominator <= 0:
        raise ValueError("at least one allocation basis must be positive")
    shares = [Decimal("0.0000") for _ in weights]
    nonzero = [index for index, value in enumerate(weights) if value > 0]
    allocated = Decimal("0")
    for index in nonzero[:-1]:
        share = (total * weights[index] / denominator).quantize(_Q)
        shares[index] = share
        allocated += share
    shares[nonzero[-1]] = total - allocated
    return shares
