using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Features.Booking.Commands.CreateBookingRequest;
using Baya.Application.Models.Booking;
using Baya.Application.Models.Identity;
using Baya.Domain.Entities.Booking;
using Baya.Domain.Entities.Catalog;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.User;
using NSubstitute;

namespace Baya.Test.Foundation.Booking;

public class CreateBookingRequestHandlerTests
{
    private readonly ICurrentUser _currentUser = Substitute.For<ICurrentUser>();
    private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
    private readonly IPlatformConfig _config = Substitute.For<IPlatformConfig>();
    private readonly IDateTimeProvider _clock = Substitute.For<IDateTimeProvider>();
    private readonly INotificationDispatcher _notifications = Substitute.For<INotificationDispatcher>();

    private readonly ICustomerProfileRepository _customers = Substitute.For<ICustomerProfileRepository>();
    private readonly IPatientRepository _patients = Substitute.For<IPatientRepository>();
    private readonly ICustomerAddressRepository _addresses = Substitute.For<ICustomerAddressRepository>();
    private readonly INurseServiceVariantRepository _variants = Substitute.For<INurseServiceVariantRepository>();
    private readonly INurseProfileRepository _nurses = Substitute.For<INurseProfileRepository>();
    private readonly IBookingRequestRepository _requests = Substitute.For<IBookingRequestRepository>();

    private const int CustomerUserId = 7;
    private const long CustomerId = 100;
    private const long NurseId = 42;
    private const int NurseUserId = 9;
    private const long PatientId = 5;
    private const long AddressId = 6;
    private const long VariantId = 8;
    private static readonly DateTimeOffset Now = new(2026, 7, 6, 10, 0, 0, TimeSpan.Zero);

    public CreateBookingRequestHandlerTests()
    {
        _currentUser.UserId.Returns(CustomerUserId);
        _currentUser.Roles.Returns([RoleNames.Customer]);
        _clock.UtcNow.Returns(Now);

        _unitOfWork.CustomerProfileRepository.Returns(_customers);
        _unitOfWork.PatientRepository.Returns(_patients);
        _unitOfWork.CustomerAddressRepository.Returns(_addresses);
        _unitOfWork.NurseServiceVariantRepository.Returns(_variants);
        _unitOfWork.NurseProfileRepository.Returns(_nurses);
        _unitOfWork.BookingRequestRepository.Returns(_requests);

        _customers.GetProfileIdByUserIdAsync(CustomerUserId, Arg.Any<CancellationToken>()).Returns(CustomerId);
        _patients.GetOwnedAsync(PatientId, CustomerId, Arg.Any<CancellationToken>())
            .Returns(new Patient { CustomerId = CustomerId, DisplayName = "پدر", Gender = "male" });
        _addresses.GetOwnedAsync(AddressId, CustomerId, Arg.Any<CancellationToken>())
            .Returns(new CustomerAddress { CustomerId = CustomerId });
        _variants.GetOwnedAsync(VariantId, NurseId, Arg.Any<CancellationToken>())
            .Returns(new NurseServiceVariant { NurseId = NurseId, IsActive = true });
        _nurses.GetBookingContextByIdAsync(NurseId, Arg.Any<CancellationToken>())
            .Returns(new NurseBookingContext(NurseUserId, "female", IsVerified: true, IsAcceptingBookings: true));
        _config.GetConfig<int>("nurse_response_deadline_hours", Arg.Any<CancellationToken>()).Returns(24);
        _requests.GetDetailAsync(Arg.Any<long>(), Arg.Any<CancellationToken>()).Returns(DetailStub());
    }

    private CreateBookingRequestCommandHandler Handler()
        => new(_currentUser, _unitOfWork, _config, _clock, _notifications);

    private static CreateBookingRequestCommand Command(string gender = "female")
        => new(NurseId, VariantId, PatientId, AddressId,
            new DateOnly(2026, 8, 1), new TimeOnly(9, 0), new TimeOnly(13, 0), gender, "Careful with the IV.");

    [Fact]
    public async Task Create_Valid_FreezesResponseDeadlineFromConfigAndNotifiesNurse()
    {
        var result = await Handler().Handle(Command(), CancellationToken.None);

        Assert.True(result.IsSuccess);
        await _requests.Received(1).AddAsync(
            Arg.Is<BookingRequest>(r =>
                r.CustomerId == CustomerId && r.NurseId == NurseId && r.PatientId == PatientId
                && r.Status == BookingRequestStatus.PendingNurseResponse
                && r.PaymentDeadlineAt == null
                && r.NurseResponseDeadlineAt == Now.AddHours(24).UtcDateTime),
            Arg.Any<CancellationToken>());
        await _unitOfWork.Received(1).CommitAsync();
        await _notifications.Received(1).DispatchAsync(
            Arg.Is<Notification>(n => n.RecipientUserId == NurseUserId && n.Type == "booking_request_received"),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_AnyGender_MatchesRegardlessOfNurseGender()
    {
        var result = await Handler().Handle(Command("any"), CancellationToken.None);

        Assert.True(result.IsSuccess);
    }

    [Fact]
    public async Task Create_SameGenderMismatch_FailsNamingTheConflict()
    {
        // Nurse is female; a male requirement must not match.
        var result = await Handler().Handle(Command("male"), CancellationToken.None);

        Assert.False(result.IsSuccess);
        Assert.False(result.IsNotFound);
        await _requests.DidNotReceive().AddAsync(Arg.Any<BookingRequest>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_CrossCustomerPatient_IsNotFoundAndCreatesNothing()
    {
        _patients.GetOwnedAsync(PatientId, CustomerId, Arg.Any<CancellationToken>()).Returns((Patient)null);

        var result = await Handler().Handle(Command(), CancellationToken.None);

        Assert.True(result.IsNotFound);
        await _requests.DidNotReceive().AddAsync(Arg.Any<BookingRequest>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_AddressNotOwned_IsNotFound()
    {
        _addresses.GetOwnedAsync(AddressId, CustomerId, Arg.Any<CancellationToken>()).Returns((CustomerAddress)null);

        var result = await Handler().Handle(Command(), CancellationToken.None);

        Assert.True(result.IsNotFound);
    }

    [Fact]
    public async Task Create_VariantNotThisNurses_IsNotFound()
    {
        _variants.GetOwnedAsync(VariantId, NurseId, Arg.Any<CancellationToken>()).Returns((NurseServiceVariant)null);

        var result = await Handler().Handle(Command(), CancellationToken.None);

        Assert.True(result.IsNotFound);
    }

    [Fact]
    public async Task Create_InactiveVariant_Fails()
    {
        _variants.GetOwnedAsync(VariantId, NurseId, Arg.Any<CancellationToken>())
            .Returns(new NurseServiceVariant { NurseId = NurseId, IsActive = false });

        var result = await Handler().Handle(Command(), CancellationToken.None);

        Assert.False(result.IsSuccess);
        await _requests.DidNotReceive().AddAsync(Arg.Any<BookingRequest>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_NurseNotAcceptingOrUnverified_Fails()
    {
        _nurses.GetBookingContextByIdAsync(NurseId, Arg.Any<CancellationToken>())
            .Returns(new NurseBookingContext(NurseUserId, "female", IsVerified: true, IsAcceptingBookings: false));

        var result = await Handler().Handle(Command(), CancellationToken.None);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task Create_PastDate_Fails()
    {
        var command = Command() with { RequestedDate = new DateOnly(2026, 7, 5) };

        var result = await Handler().Handle(command, CancellationToken.None);

        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task Create_NonCustomer_IsForbidden()
    {
        _currentUser.Roles.Returns([RoleNames.Nurse]);

        var result = await Handler().Handle(Command(), CancellationToken.None);

        Assert.True(result.IsForbidden);
    }

    private static BookingRequestDetailProjection DetailStub()
        => new(
            1, BookingRequestStatus.PendingNurseResponse, CustomerId, NurseId,
            "پرستار", 0m, 0, PatientId, "پدر", VariantId, "خدمت", "per_day",
            AddressId, "خانه", 1, "تهران", "Tehran", null, null, null,
            "line", "postal", "recipient", "phone",
            "female", new DateOnly(2026, 8, 1), new TimeOnly(9, 0), new TimeOnly(13, 0),
            "notes", Now.AddHours(24).UtcDateTime, null, null, Now,
            1_000_000, null, null);
}
