"""Per-currency money aggregation (04-architecture §Money paths).

Aggregates are per-currency maps so a naive cross-currency sum is
unrepresentable. R1 data is all-CAD (07 #21); every aggregate SELECT
carries `count(DISTINCT <currency col>)` next to its SUM and this
module asserts it — a mixed-currency window ERRORS LOUDLY instead of
adding apples to oranges.
"""

from __future__ import annotations

from dataclasses import dataclass
from decimal import ROUND_HALF_UP, Decimal


class MixedCurrencyError(Exception):
    """More than one currency inside a single aggregation window —
    unaggregatable in R1 (02 §3 segregation rule)."""

    def __init__(self, context: str, distinct_count: int):
        super().__init__(
            f"{context}: {distinct_count} distinct currencies in one aggregate —"
            " R1 money windows must be single-currency"
        )
        self.context = context


@dataclass(frozen=True)
class Money:
    """The one DTO money shape: decimal-as-string + currency code.
    Rendered by MoneyCell, never arithmetic on the frontend."""

    amount: str
    currency: str

    def as_dict(self) -> dict[str, str]:
        return {"amount": self.amount, "currency": self.currency}


def quantize2(value: Decimal) -> Decimal:
    """Cents, ROUND_HALF_UP — the house rounding (rpcs §2.6; harness
    common.qstr)."""
    q = value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    return abs(q) if q == 0 else q  # normalize -0.00


def money_from_sum(
    total: Decimal | None,
    distinct_currencies: int,
    currency: str | None,
    *,
    context: str,
    zero_currency: str,
) -> Money:
    """Fold one SQL (SUM, COUNT(DISTINCT currency), MIN(currency))
    triple into Money. All-NULL sums are a real zero in the tenant's
    default currency (harness money_sum parity); >1 currency raises."""
    if distinct_currencies > 1:
        raise MixedCurrencyError(context, distinct_currencies)
    if total is None:
        return Money(amount="0.00", currency=zero_currency)
    return Money(amount=str(quantize2(Decimal(total))), currency=currency or zero_currency)


def money_or_none(value: Decimal | None, currency: str | None, zero_currency: str) -> Money | None:
    """Scalar money column -> Money | None (NULL = never priced)."""
    if value is None:
        return None
    return Money(amount=str(quantize2(Decimal(value))), currency=currency or zero_currency)
