#nullable enable
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
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.TransitionBookingStatus;

internal sealed class TransitionBookingStatusCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IPlatformConfig platformConfig,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<TransitionBookingStatusCommand, OperationResult<BookingDetailDto>>
{
    public async ValueTask<OperationResult<BookingDetailDto>> Handle(TransitionBookingStatusCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } _)
            return OperationResult<BookingDetailDto>.UnauthorizedResult("Not authenticated.");

        if (currentUser.Roles?.Any(BookingRoles.Admin.Contains) != true)
            return OperationResult<BookingDetailDto>.ForbiddenResult("Only an admin can drive an explicit booking transition.");

        var target = request.TargetStatus;
        if (target == BookingStatus.Cancelled)
            return OperationResult<BookingDetailDto>.FailureResult("Use the cancel endpoint so the cancellation policy is resolved and snapshotted.");

        var booking = await unitOfWork.BookingRepository.GetTrackedWithSessionsAsync(request.BookingId, cancellationToken);
        if (booking is null)
            return OperationResult<BookingDetailDto>.NotFoundResult("Booking not found.");

        if (!booking.CanTransitionTo(target))
            return OperationResult<BookingDetailDto>.ConflictResult($"A booking in '{booking.Status}' cannot transition to '{target}'.");

        // No transition may contradict EVV/session state.
        if (target == BookingStatus.InProgress &&
            !booking.Sessions.Any(s => s.Status == BookingSessionStatus.InProgress))
            return OperationResult<BookingDetailDto>.ConflictResult("Cannot move to in_progress with no session checked in.");

        if (target == BookingStatus.Completed &&
            booking.Sessions.Any(s => s.Status is BookingSessionStatus.Scheduled or BookingSessionStatus.InProgress))
            return OperationResult<BookingDetailDto>.ConflictResult("Cannot complete a booking while a session is still scheduled or in progress.");

        var now = dateTimeProvider.UtcNow.UtcDateTime;
        booking.TransitionTo(target, now, reason: request.Reason);

        // An admin-driven completion still opens the dispute window (the single payout-eligibility trigger).
        if (target == BookingStatus.Completed)
        {
            var disputeWindowHours = await platformConfig.GetConfig<int>("dispute_window_hours", cancellationToken);
            booking.SetDisputeWindow(now.AddHours(disputeWindowHours));
        }

        await unitOfWork.CommitAsync();

        var detail = await unitOfWork.BookingRepository.GetDetailAsync(booking.Id, cancellationToken);
        return OperationResult<BookingDetailDto>.SuccessResult(BookingMapper.ToDetailDto(detail!, includeAddress: true));
    }
}
