using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Application.Features.Bookings.Commands.DetectNoShowSessions;
using Baya.Application.Features.Payouts.Commands.GeneratePayoutBatch;
using Baya.Application.Features.Verification.Commands.ScanExpiringCredentials;
using Baya.Application.Models.Booking;
using Baya.Application.Models.Common;
using Baya.Application.Models.Payouts;
using Baya.Application.Models.Verification;
using Baya.Infrastructure.Persistence.Services.Scheduling.Jobs;
using Mediator;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;

namespace Baya.Test.Foundation.Scheduling;

/// <summary>
/// Unit tests for the recurring jobs (refinement-phase-7): each reads its seeded cadence key and dispatches the
/// same idempotent command the admin manual trigger sends. No timers involved — pure job behaviour.
/// </summary>
public sealed class RecurringJobTests
{
    private static IServiceProvider ProviderWith(params (Type Type, object Impl)[] services)
    {
        var provider = Substitute.For<IServiceProvider>();
        foreach (var (type, impl) in services)
            provider.GetService(type).Returns(impl);
        return provider;
    }

    [Fact]
    public async Task CredentialExpiryScan_ReadsCadenceKey_AndSendsScanCommand()
    {
        var config = Substitute.For<IPlatformConfig>();
        config.GetConfig<int>("verification_expiry_scan_cadence_hours", Arg.Any<CancellationToken>())
            .Returns(new ValueTask<int>(6));
        var sender = Substitute.For<ISender>();
        sender.Send(Arg.Any<ScanExpiringCredentialsCommand>(), Arg.Any<CancellationToken>())
            .Returns(OperationResult<ScanExpiringResult>.SuccessResult(new ScanExpiringResult(0, 0)));

        var job = new CredentialExpiryScanJob(NullLogger<CredentialExpiryScanJob>.Instance);
        var provider = ProviderWith((typeof(IPlatformConfig), config), (typeof(ISender), sender));

        var interval = await job.GetIntervalAsync(provider, default);
        await job.RunAsync(provider, default);

        Assert.Equal(TimeSpan.FromHours(6), interval);
        await sender.Received(1).Send(Arg.Any<ScanExpiringCredentialsCommand>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task NoShowSweep_ReadsCadenceKey_AndSendsDetectCommand()
    {
        var config = Substitute.For<IPlatformConfig>();
        config.GetConfig<int>("no_show_scan_cadence_hours", Arg.Any<CancellationToken>())
            .Returns(new ValueTask<int>(1));
        var sender = Substitute.For<ISender>();
        sender.Send(Arg.Any<DetectNoShowSessionsCommand>(), Arg.Any<CancellationToken>())
            .Returns(OperationResult<NoShowSweepResult>.SuccessResult(new NoShowSweepResult(0)));

        var job = new NoShowSweepJob(NullLogger<NoShowSweepJob>.Instance);
        var provider = ProviderWith((typeof(IPlatformConfig), config), (typeof(ISender), sender));

        var interval = await job.GetIntervalAsync(provider, default);
        await job.RunAsync(provider, default);

        Assert.Equal(TimeSpan.FromHours(1), interval);
        await sender.Received(1).Send(Arg.Any<DetectNoShowSessionsCommand>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task WeeklyPayoutGeneration_ReadsInterval_AndSendsSystemInitiatedBatchOverTrailingWindow()
    {
        var config = Substitute.For<IPlatformConfig>();
        config.GetConfig<int>("nurse_payout_interval_days", Arg.Any<CancellationToken>())
            .Returns(new ValueTask<int>(7));
        var clock = Substitute.For<IDateTimeProvider>();
        clock.UtcNow.Returns(new DateTimeOffset(2026, 7, 13, 9, 0, 0, TimeSpan.Zero));
        var sender = Substitute.For<ISender>();
        sender.Send(Arg.Any<GeneratePayoutBatchCommand>(), Arg.Any<CancellationToken>())
            .Returns(OperationResult<GeneratePayoutBatchResult>.SuccessResult(
                new GeneratePayoutBatchResult(
                    new PayoutBatchDto(1, new DateOnly(2026, 7, 6), new DateOnly(2026, 7, 13),
                        new DateOnly(2026, 7, 13), "0", 0, "draft", null, null, null, DateTimeOffset.UtcNow),
                    [], [])));

        var job = new WeeklyPayoutGenerationJob(NullLogger<WeeklyPayoutGenerationJob>.Instance);
        var provider = ProviderWith(
            (typeof(IPlatformConfig), config), (typeof(IDateTimeProvider), clock), (typeof(ISender), sender));

        var interval = await job.GetIntervalAsync(provider, default);
        await job.RunAsync(provider, default);

        Assert.Equal(TimeSpan.FromDays(7), interval);
        await sender.Received(1).Send(
            Arg.Is<GeneratePayoutBatchCommand>(c =>
                c.SystemInitiated
                && c.PeriodEnd == new DateOnly(2026, 7, 13)
                && c.PeriodStart == new DateOnly(2026, 7, 6)),
            Arg.Any<CancellationToken>());
    }
}
