using Baya.Application.Contracts.Common;
using Baya.Application.Features.Booking.Commands.ExpireBookingRequests;
using Baya.Domain.Entities.Booking;
using NSubstitute;

namespace Baya.Test.Foundation.Booking;

public class BookingRequestExpiryTests : IDisposable
{
    private readonly BookingTestHost _host = new();
    private readonly IDateTimeProvider _clock = Substitute.For<IDateTimeProvider>();
    private readonly INotificationDispatcher _notifications = Substitute.For<INotificationDispatcher>();

    private static readonly DateTimeOffset Now = new(2026, 7, 6, 12, 0, 0, TimeSpan.Zero);
    private static readonly DateTime NowUtc = Now.UtcDateTime;

    private ExpireBookingRequestsCommandHandler Handler()
    {
        _clock.UtcNow.Returns(Now);
        return new ExpireBookingRequestsCommandHandler(_host.UnitOfWork, _clock, _notifications);
    }

    [Fact]
    public async Task Sweep_TransitionsStalePendingAndAcceptedAndNotifies()
    {
        var stalePending = _host.AddPendingRequest(responseDeadline: NowUtc.AddHours(-1));
        var freshPending = _host.AddPendingRequest(responseDeadline: NowUtc.AddHours(5));
        var staleAccepted = _host.AddAcceptedRequest(paymentDeadline: NowUtc.AddMinutes(-1));
        var freshAccepted = _host.AddAcceptedRequest(paymentDeadline: NowUtc.AddMinutes(20));

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

        Assert.True(result.IsSuccess);
        Assert.Equal(1, result.Result.ExpiredNoResponse);
        Assert.Equal(1, result.Result.PaymentDeadlineExpired);

        Assert.Equal(BookingRequestStatus.ExpiredNoResponse, _host.StatusOf(stalePending.Id));
        Assert.Equal(BookingRequestStatus.PendingNurseResponse, _host.StatusOf(freshPending.Id));
        Assert.Equal(BookingRequestStatus.PaymentDeadlineExpired, _host.StatusOf(staleAccepted.Id));
        Assert.Equal(BookingRequestStatus.AcceptedAwaitingPayment, _host.StatusOf(freshAccepted.Id));

        await _notifications.Received(2).DispatchAsync(Arg.Any<Notification>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Sweep_IsIdempotent_SecondRunIsNoOp()
    {
        _host.AddPendingRequest(responseDeadline: NowUtc.AddHours(-1));

        var first = await Handler().Handle(new ExpireBookingRequestsCommand(), CancellationToken.None);
        var second = await Handler().Handle(new ExpireBookingRequestsCommand(), CancellationToken.None);

        Assert.Equal(1, first.Result.ExpiredNoResponse);
        Assert.Equal(0, second.Result.ExpiredNoResponse);
        Assert.Equal(0, second.Result.PaymentDeadlineExpired);
    }

    public void Dispose() => _host.Dispose();
}
