#nullable enable
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Common;
using Baya.Domain.Entities.Payouts;
using Mediator;

namespace Baya.Application.Features.Payouts.Commands.MarkPayoutFailed;

/// <summary>Records a reconciled bank rejection on a payout. No ledger movement — a rejected transfer means no
/// money left, so there is nothing to reverse. Re-settles the parent batch so its status reflects the failure.</summary>
internal sealed class MarkPayoutFailedCommandHandler(
    IUnitOfWork unitOfWork,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<MarkPayoutFailedCommand, OperationResult<bool>>
{
    public async ValueTask<OperationResult<bool>> Handle(MarkPayoutFailedCommand request, CancellationToken cancellationToken)
    {
        var now = dateTimeProvider.UtcNow.UtcDateTime;

        var stub = await unitOfWork.PayoutRepository.GetTrackedPayoutAsync(request.PayoutId, cancellationToken);
        if (stub is null)
            return OperationResult<bool>.NotFoundResult("Payout not found.");

        var batch = await unitOfWork.PayoutRepository.GetTrackedBatchAsync(stub.BatchId, cancellationToken);
        var payout = batch!.Payouts.First(p => p.Id == request.PayoutId);

        // A paid payout is a confirmed, irreversible transfer — it can never be marked failed.
        if (payout.Status == PayoutStatus.Paid)
            return OperationResult<bool>.ConflictResult("A paid payout cannot be marked failed.");
        // Idempotent.
        if (payout.Status == PayoutStatus.Failed)
            return OperationResult<bool>.SuccessResult(true);

        payout.MarkFailed(request.FailureReason);
        batch.RecomputeSettlement(now);

        await unitOfWork.CommitAsync();
        return OperationResult<bool>.SuccessResult(true);
    }
}
