#nullable enable
using System.Text.Json;
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 Baya.Domain.Entities.User;
using Mediator;

namespace Baya.Application.Features.Booking.Commands.RejectBookingRequest;

internal sealed class RejectBookingRequestCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    INotificationDispatcher notifications)
    : IRequestHandler<RejectBookingRequestCommand, OperationResult<BookingRequestDto>>
{
    public async ValueTask<OperationResult<BookingRequestDto>> Handle(RejectBookingRequestCommand 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 reject 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.RejectedByNurse))
            return OperationResult<BookingRequestDto>.ConflictResult("This request can no longer be rejected.");

        bookingRequest.Reject(request.Reason.Trim());

        await unitOfWork.CommitAsync();

        await notifications.DispatchAsync(
            new Notification(
                bookingRequest.Customer.UserId,
                "booking_request_rejected",
                "Booking request declined",
                "The nurse declined your request.",
                JsonSerializer.Serialize(new
                {
                    booking_request_id = bookingRequest.Id,
                    reason = bookingRequest.NurseRejectionReason
                })),
            cancellationToken);

        var detail = await unitOfWork.BookingRequestRepository.GetDetailAsync(bookingRequest.Id, cancellationToken);
        return OperationResult<BookingRequestDto>.SuccessResult(BookingRequestMapper.ToDto(detail!, includeFullAddress: false));
    }
}
