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

namespace Baya.Application.Features.Bookings.Commands.CancelSession;

internal sealed class CancelSessionCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<CancelSessionCommand, OperationResult<CancellationResultDto>>
{
    public async ValueTask<OperationResult<CancellationResultDto>> Handle(CancelSessionCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<CancellationResultDto>.UnauthorizedResult("Not authenticated.");

        var booking = await unitOfWork.BookingRepository.GetTrackedBookingBySessionAsync(request.SessionId, cancellationToken);
        var session = booking?.Sessions.FirstOrDefault(s => s.Id == request.SessionId);
        if (booking is null || session is null)
            return OperationResult<CancellationResultDto>.NotFoundResult("Session not found.");

        var isAdmin = currentUser.Roles?.Any(BookingRoles.Admin.Contains) == true;
        var customerId = await unitOfWork.CustomerProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
        var nurseId = await unitOfWork.NurseProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
        var isOwningCustomer = customerId == booking.CustomerId;
        var isAssignedNurse = nurseId == booking.NurseId;

        if (!isAdmin && !isOwningCustomer && !isAssignedNurse)
            return OperationResult<CancellationResultDto>.NotFoundResult("Session not found.");

        // Only an un-started session can be cancelled (and refunded); a started/finished visit cannot.
        if (session.Status != BookingSessionStatus.Scheduled)
            return OperationResult<CancellationResultDto>.ConflictResult("Only an un-started session can be cancelled.");

        var now = dateTimeProvider.UtcNow.UtcDateTime;
        var actor = CancellationHelper.ResolveActor(isAdmin, isAssignedNurse && !isOwningCustomer);

        var policies = await unitOfWork.CancellationPolicyRepository.GetActiveForActorAsync(actor, cancellationToken);
        var hoursBefore = CancellationHelper.HoursBeforeStart(session.ScheduledDate, session.ScheduledTimeStart, now);
        var policy = CancellationHelper.ResolvePolicy(policies, hoursBefore);
        if (policy is null)
            return OperationResult<CancellationResultDto>.FailureResult("No cancellation policy applies to this session.");

        var grossShares = BookingAmounts.SplitPayout(booking.GrossPriceIrr, booking.SessionCount);
        var refundableBase = grossShares[session.SessionIndex - 1];
        var refundable = CancellationHelper.Refundable(refundableBase, policy.RefundPercentage);

        session.TransitionTo(BookingSessionStatus.Cancelled);
        // The booking keeps the most recent cancellation snapshot; the typed per-event record lands in b11.
        booking.RecordCancellationSnapshot(policy.Code, policy.RefundPercentage, refundable);

        await unitOfWork.CommitAsync();

        return OperationResult<CancellationResultDto>.SuccessResult(new CancellationResultDto(
            booking.Id, session.Id, booking.Status, policy.Code, policy.RefundPercentage, refundable.ToString()));
    }
}
