#nullable enable
using Baya.Application.Contracts.Payments;
using Baya.Application.Contracts.Persistence;
using Baya.Domain.Entities.Payments;
using Baya.Domain.Entities.Payouts;

namespace Baya.Application.Features.Payouts;

/// <summary>
/// The shared money-settlement steps for a paid payout, used by both <c>ExecutePayoutBatch</c> and
/// <c>RetryFailedPayout</c> so the ledger posting + clawback netting live in exactly one place (mirroring b12's
/// extracted <c>BookingConversion</c>). Both operations are idempotent: the ledger-exists guard blocks a second
/// payout group, and a <c>recovered</c> clawback is never re-marked or re-posted.
/// </summary>
internal static class PayoutSettlement
{
    /// <summary>Builds the rail instruction — SATNA above the config threshold, else PAYA. The tracked payout's
    /// <see cref="NursePayout.IbanSnapshot"/> is decrypted by the EF converter on load.</summary>
    public static PayoutInstruction ToInstruction(NursePayout payout, long satnaThreshold)
        => new(payout.Id, payout.IbanSnapshot, payout.Amount,
            payout.Amount >= satnaThreshold ? BankTransferMethod.Satna : BankTransferMethod.Paya);

    /// <summary>DEBIT <c>nurse_payable</c> / CREDIT <c>escrow_held</c> for the paid net — skipped when the payout
    /// is fully netted (net 0) or a group already exists (retry idempotency).</summary>
    public static async Task PostPayoutLedgerAsync(IUnitOfWork unitOfWork, NursePayout payout, DateTime now, CancellationToken cancellationToken)
    {
        if (payout.Amount <= 0)
            return;
        if (await unitOfWork.PayoutRepository.LedgerGroupExistsForPayoutAsync(payout.Id, cancellationToken))
            return;

        var legs = LedgerPosting.NursePayout(payout.NurseId, payout.Amount, payout.Id, now);
        await unitOfWork.PaymentRepository.AddLedgerEntriesAsync(legs, cancellationToken);
    }

    /// <summary>Realizes the payout's frozen <c>clawback_applied_irr</c>: recovers whole pending clawbacks (oldest
    /// first, matching the build's greedy cap) — marks each <c>recovered</c> + posts DEBIT <c>nurse_payable</c> /
    /// CREDIT <c>nurse_clawback_receivable</c>. Idempotent: on a retry the clawbacks are already <c>recovered</c>.</summary>
    public static async Task RecoverClawbacksAsync(IUnitOfWork unitOfWork, NursePayout payout, DateTime now, CancellationToken cancellationToken)
    {
        if (payout.ClawbackAppliedIrr <= 0)
            return;

        var pending = await unitOfWork.PayoutRepository.GetPendingClawbacksAsync(payout.NurseId, cancellationToken);
        var remaining = payout.ClawbackAppliedIrr;

        foreach (var clawback in pending)
        {
            if (clawback.AmountIrr > remaining)
                break;

            clawback.Recover(payout.Id, now);
            var legs = LedgerPosting.ClawbackRecovery(clawback.BookingId, clawback.NurseId, clawback.AmountIrr, clawback.Id, now);
            await unitOfWork.PaymentRepository.AddLedgerEntriesAsync(legs, cancellationToken);
            remaining -= clawback.AmountIrr;
        }
    }
}
