#nullable enable
using System.Text.Json;
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.Booking.Commands.AcceptBookingRequest;

internal sealed class AcceptBookingRequestCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IPlatformConfig platformConfig,
    IDateTimeProvider dateTimeProvider,
    INotificationDispatcher notifications)
    : IRequestHandler<AcceptBookingRequestCommand, OperationResult<BookingRequestDto>>
{
    public async ValueTask<OperationResult<BookingRequestDto>> Handle(AcceptBookingRequestCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<BookingRequestDto>.UnauthorizedResult("Not authenticated.");

        if (currentUser.Roles?.Contains(RoleNames.Nurse) != true)
            return OperationResult<BookingRequestDto>.ForbiddenResult("Only a nurse can accept a booking request.");

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

        var bookingRequest = await unitOfWork.BookingRequestRepository.GetTrackedForNurseAsync(request.Id, nid, cancellationToken);
        if (bookingRequest is null)
            return OperationResult<BookingRequestDto>.NotFoundResult("Booking request not found.");

        if (!bookingRequest.CanTransitionTo(BookingRequestStatus.AcceptedAwaitingPayment))
            return OperationResult<BookingRequestDto>.ConflictResult("This request can no longer be accepted.");

        var now = dateTimeProvider.UtcNow.UtcDateTime;

        // Self-guard against an already-passed response deadline — the expiry sweep may not have run yet, so
        // the command must not trust that it has.
        if (bookingRequest.NurseResponseDeadlineAt <= now)
            return OperationResult<BookingRequestDto>.ConflictResult("The response deadline has passed; this request can no longer be accepted.");

        // Frozen from config, never the literal 30.
        var paymentDeadlineMinutes = await platformConfig.GetConfig<int>("booking_payment_deadline_minutes", cancellationToken);
        bookingRequest.Accept(now.AddMinutes(paymentDeadlineMinutes));

        await unitOfWork.CommitAsync();

        await notifications.DispatchAsync(
            new Notification(
                bookingRequest.Customer.UserId,
                "booking_request_accepted",
                "Booking request accepted",
                "The nurse accepted your request. Pay within the payment window to confirm your booking.",
                JsonSerializer.Serialize(new
                {
                    booking_request_id = bookingRequest.Id,
                    payment_deadline_at = bookingRequest.PaymentDeadlineAt?.ToString("O")
                })),
            cancellationToken);

        var detail = await unitOfWork.BookingRequestRepository.GetDetailAsync(bookingRequest.Id, cancellationToken);
        // Nurse view — stage-1 disclosure masks the encrypted full address.
        return OperationResult<BookingRequestDto>.SuccessResult(BookingRequestMapper.ToDto(detail!, includeFullAddress: false));
    }
}
