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

namespace Baya.Application.Features.Refunds.Commands.MarkRefundSettlementFailed;

/// <summary>
/// Fails a <c>processing</c> BNPL/manual refund whose customer cash-back reconciliation did not succeed. No ledger
/// moves (the clearing was never posted for a processing refund). Runs under the <c>booking:{id}:refund</c> lock
/// and re-reads the row inside it so a racing confirm/fail is serialized and a replay no-ops.
/// </summary>
internal sealed class MarkRefundSettlementFailedCommandHandler(
    IUnitOfWork unitOfWork,
    IDistributedLock distributedLock,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<MarkRefundSettlementFailedCommand, OperationResult<RefundSettlementResult>>
{
    public async ValueTask<OperationResult<RefundSettlementResult>> Handle(MarkRefundSettlementFailedCommand request, CancellationToken cancellationToken)
    {
        var projection = await unitOfWork.RefundRepository.GetStatusAsync(request.RefundId, cancellationToken);
        if (projection is null)
            return OperationResult<RefundSettlementResult>.NotFoundResult("Refund not found.");

        await using var _ = await distributedLock.AcquireAsync($"booking:{projection.Refund.BookingId}:refund", cancellationToken);

        var refund = await unitOfWork.RefundRepository.GetTrackedRefundByIdAsync(request.RefundId, cancellationToken);
        if (refund is null)
            return OperationResult<RefundSettlementResult>.NotFoundResult("Refund not found.");

        // Idempotent: already-failed is a no-op success.
        if (refund.Status == RefundStatus.Failed)
            return Result(refund);

        if (refund.Status != RefundStatus.Processing)
            return OperationResult<RefundSettlementResult>.ConflictResult(
                $"Only a processing refund can be failed (was {refund.Status}).");

        refund.MarkFailed(request.Reason ?? "settlement_failed");
        await unitOfWork.CommitAsync();

        return Result(refund);
    }

    private static OperationResult<RefundSettlementResult> Result(Refund refund)
        => OperationResult<RefundSettlementResult>.SuccessResult(
            new RefundSettlementResult(refund.Id, refund.BookingId, refund.Status, refund.ProcessedAt));
}
