#nullable enable
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Features.Refunds.Commands.CreateRefund;
using Baya.Application.Models.Bnpl;
using Baya.Application.Models.Common;
using Baya.Domain.Entities.Bnpl;
using Mediator;

namespace Baya.Application.Features.Bnpl.Commands.RevertBnplOrder;

internal sealed class RevertBnplOrderCommandHandler(
    ISender sender,
    IUnitOfWork unitOfWork,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<RevertBnplOrderCommand, OperationResult<RevertBnplResult>>
{
    public async ValueTask<OperationResult<RevertBnplResult>> Handle(RevertBnplOrderCommand request, CancellationToken cancellationToken)
    {
        var bnpl = await unitOfWork.BnplRepository.GetTrackedByIdAsync(request.BnplTransactionId, cancellationToken);
        if (bnpl is null)
            return OperationResult<RevertBnplResult>.NotFoundResult("BNPL order not found.");

        // A replayed revert must not double-refund — an already-reverted order is a clean conflict.
        if (bnpl.Status == BnplStatus.Reverted)
            return OperationResult<RevertBnplResult>.ConflictResult("This BNPL order has already been reverted.");
        // Only a settled order has a captured booking + ledger to reverse.
        if (bnpl.Status != BnplStatus.Settled)
            return OperationResult<RevertBnplResult>.ConflictResult("Only a settled BNPL order can be reverted.");

        var transaction = await unitOfWork.PaymentRepository.GetTrackedTransactionByIdAsync(bnpl.PaymentTransactionId, cancellationToken);
        if (transaction?.BookingId is not { } bookingId)
            return OperationResult<RevertBnplResult>.ConflictResult("The BNPL order has no captured booking to reverse.");

        // Reuse the b11 refund path: it creates the refunds row (refund_channel='bnpl_revert'), executes the
        // provider revert/update behind the seam, posts the balanced reversal ledger (+ clawback fork), and
        // surfaces the async customer ETA. It owns lock(booking:{id}:refund), so we do not lock here.
        var refund = await sender.Send(new CreateRefundCommand(
            BookingId: bookingId,
            TicketId: request.TicketId,
            RefundPercentage: request.RefundPercentage ?? 1m,
            PlatformFeeRefundedIrr: null,
            NursePayoutRefundedIrr: null,
            ReasonCategory: "bnpl_revert",
            ReasonNotes: request.ReasonNotes,
            AdminNotes: null,
            ManualBankReference: null), cancellationToken);

        if (!refund.IsSuccess)
            return new OperationResult<RevertBnplResult>
            {
                IsSuccess = false,
                IsNotFound = refund.IsNotFound,
                IsConflict = refund.IsConflict,
                IsForbidden = refund.IsForbidden,
                IsUnauthorized = refund.IsUnauthorized,
                IsException = refund.IsException,
                ErrorMessages = refund.ErrorMessages
            };

        var refundResult = refund.Result;
        var revertedAmount = long.TryParse(refundResult.Amount, out var amount) ? amount : 0;
        var externalRef = await unitOfWork.RefundRepository.GetExternalRevertReferenceAsync(refundResult.RefundId, cancellationToken);

        var now = dateTimeProvider.UtcNow.UtcDateTime;
        // provider_commission_reversed_amount is reconciled from the provider response later — nullable here
        // (some providers keep their fee on a refund; the b11 refund path does not persist it).
        bnpl.MarkReverted(externalRef, revertedAmount, providerCommissionReversedAmount: null, now, request.CallbackPayloadJson);
        await unitOfWork.CommitAsync();

        return OperationResult<RevertBnplResult>.SuccessResult(new RevertBnplResult(
            bnpl.Id, refundResult.RefundId, bnpl.Status, externalRef,
            revertedAmount.ToString(), refundResult.ExpectedCustomerRefundEta));
    }
}
