#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 Baya.Domain.Entities.User;
using Mediator;

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

internal sealed class CheckOutVisitCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IPlatformConfig platformConfig,
    IDateTimeProvider dateTimeProvider)
    : IRequestHandler<CheckOutVisitCommand, OperationResult<VisitVerificationDto>>
{
    // A session is "settled" for booking-completion purposes when it can no longer become in_progress.
    private static readonly string[] TerminalSessionStatuses =
        [BookingSessionStatus.Completed, BookingSessionStatus.Missed, BookingSessionStatus.Cancelled];

    public async ValueTask<OperationResult<VisitVerificationDto>> Handle(CheckOutVisitCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<VisitVerificationDto>.UnauthorizedResult("Not authenticated.");

        if (currentUser.Roles?.Contains(RoleNames.Nurse) != true)
            return OperationResult<VisitVerificationDto>.ForbiddenResult("Only a nurse can check out.");

        var nurseId = await unitOfWork.NurseProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
        if (nurseId is not { } nid)
            return OperationResult<VisitVerificationDto>.ForbiddenResult("No nurse profile exists yet.");

        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<VisitVerificationDto>.NotFoundResult("Session not found.");

        if (booking.NurseId != nid)
            return OperationResult<VisitVerificationDto>.NotFoundResult("Session not found.");

        var verification = session.Verification;
        if (verification is null || verification.Status != VisitVerificationStatus.CheckedIn)
            return OperationResult<VisitVerificationDto>.FailureResult("Check-out must follow an open check-in.");

        if (!session.CanTransitionTo(BookingSessionStatus.Completed))
            return OperationResult<VisitVerificationDto>.ConflictResult("This session cannot be checked out.");

        var now = dateTimeProvider.UtcNow.UtcDateTime;
        var disputeWindowHours = await platformConfig.GetConfig<int>("dispute_window_hours", cancellationToken);

        verification.CheckOutAt = now;
        verification.CheckOutLat = request.Latitude;
        verification.CheckOutLng = request.Longitude;
        verification.MarkCompleted();

        session.TransitionTo(BookingSessionStatus.Completed);
        // Per-session payout gate — the ONLY thing that makes this session payout-eligible (b13). Never the
        // completed status alone.
        session.SetPayoutEligible(now.AddHours(disputeWindowHours));

        // When every session is settled, the booking completes and its dispute window opens.
        var allSettled = booking.Sessions.All(s => TerminalSessionStatuses.Contains(s.Status));
        if (allSettled && booking.CanTransitionTo(BookingStatus.Completed))
        {
            booking.TransitionTo(BookingStatus.Completed, now);
            booking.SetDisputeWindow(now.AddHours(disputeWindowHours));
        }

        await unitOfWork.CommitAsync();

        var gate = await unitOfWork.BookingRepository.GetEvvForSessionAsync(session.Id, cancellationToken);
        return OperationResult<VisitVerificationDto>.SuccessResult(gate!.Evv!);
    }
}
