#nullable enable
using Baya.Application.Contracts.Common;
using Baya.Application.Features.Bookings.Commands.CancelBooking;
using Baya.Application.Features.Refunds.Commands.CreateRefund;
using Baya.Application.Features.Refunds.Queries.GetRefundStatus;
using Baya.Application.Models.Common;
using Baya.Application.Models.Refunds;
using Baya.Domain.Entities.User;
using Mediator;

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

internal sealed class CancelBookingAndRefundCommandHandler(
    ICurrentUser currentUser,
    ISender sender)
    : IRequestHandler<CancelBookingAndRefundCommand, OperationResult<RefundStatusDto>>
{
    public async ValueTask<OperationResult<RefundStatusDto>> Handle(CancelBookingAndRefundCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is null)
            return OperationResult<RefundStatusDto>.UnauthorizedResult("Not authenticated.");

        if (currentUser.Roles?.Contains(RoleNames.Customer) != true)
            return OperationResult<RefundStatusDto>.ForbiddenResult("Only a customer can cancel their own booking.");

        var reason = string.IsNullOrWhiteSpace(request.ReasonNotes)
            ? request.ReasonCategory
            : $"{request.ReasonCategory}: {request.ReasonNotes}";

        // 1) Cancel the booking — resolves + freezes the customer cancellation policy snapshot (owner-scoped
        // inside the handler; a foreign booking is a clean 404, a terminal one a 409).
        var cancel = await sender.Send(new CancelBookingCommand(reason, request.BookingId), cancellationToken);
        if (!cancel.IsSuccess)
            return Propagate(cancel);

        // 2) Open the refund off the just-frozen snapshot (decomposes both fee legs, posts the reversal,
        // auto-opens a refund ticket). The customer never sets the money — the snapshot drives it.
        var refund = await sender.Send(
            new CreateRefundCommand(
                BookingId: request.BookingId,
                TicketId: null,
                RefundPercentage: null,
                PlatformFeeRefundedIrr: null,
                NursePayoutRefundedIrr: null,
                ReasonCategory: request.ReasonCategory,
                ReasonNotes: request.ReasonNotes,
                AdminNotes: null,
                ManualBankReference: null),
            cancellationToken);
        if (!refund.IsSuccess)
            return Propagate(refund);

        // Return the full customer status (incl. the decomposition + policy snapshot) — owner-scoped read.
        return await sender.Send(new GetRefundStatusQuery(refund.Result.RefundId), cancellationToken);
    }

    private static OperationResult<RefundStatusDto> Propagate<T>(OperationResult<T> failed)
    {
        var message = failed.ErrorMessages.Count > 0 ? failed.ErrorMessages[0].Value : "The cancellation could not be completed.";
        if (failed.IsNotFound)
            return OperationResult<RefundStatusDto>.NotFoundResult(message);
        if (failed.IsConflict)
            return OperationResult<RefundStatusDto>.ConflictResult(message);
        if (failed.IsForbidden)
            return OperationResult<RefundStatusDto>.ForbiddenResult(message);
        if (failed.IsUnauthorized)
            return OperationResult<RefundStatusDto>.UnauthorizedResult(message);
        return OperationResult<RefundStatusDto>.FailureResult(message);
    }
}
