from decimal import Decimal, ROUND_HALF_UP

TWOPLACES = Decimal("0.01")


def money(value):
    return Decimal(str(value)).quantize(TWOPLACES, rounding=ROUND_HALF_UP)


def build_installment_amounts(principal, installment_count):
    """Return exact installment amounts whose sum equals principal.

    Equal rounded installments are used and the final installment absorbs
    the rounding remainder. This avoids silently leaving a residual balance.
    """
    principal = money(principal)
    count = int(installment_count)
    if principal <= 0:
        raise ValueError("principal must be greater than zero")
    if count <= 0:
        raise ValueError("installment_count must be greater than zero")

    regular = money(principal / Decimal(count))
    amounts = []
    remaining = principal
    for index in range(count):
        amount = remaining if index == count - 1 else min(regular, remaining)
        amount = money(amount)
        amounts.append(amount)
        remaining = money(remaining - amount)
    if sum(amounts, Decimal("0.00")) != principal:
        raise ValueError("installment schedule does not reconcile to principal")
    return amounts


def allocate_recovery(unpaid_installments, maximum_amount):
    """Allocate a payroll recovery oldest-first.

    unpaid_installments is an iterable of (installment_id, unpaid_amount).
    Returns [(installment_id, allocated_amount), ...].
    """
    remaining = money(maximum_amount)
    if remaining < 0:
        raise ValueError("maximum_amount cannot be negative")
    allocations = []
    for installment_id, unpaid in unpaid_installments:
        unpaid = money(unpaid)
        if remaining <= 0:
            break
        if unpaid <= 0:
            continue
        amount = min(unpaid, remaining)
        allocations.append((installment_id, money(amount)))
        remaining = money(remaining - amount)
    return allocations
