using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Xunit;

namespace AsbCloudInfrastructure.Tests.Background;

//TODO: нужно поправить тесты, иногда они не проходят
public class PeriodicBackgroundWorkerTest
{
    private IServiceProvider provider;
    private PeriodicBackgroundWorker service;

    public PeriodicBackgroundWorkerTest()
    {
        provider = Substitute.For<IServiceProvider, ISupportRequiredService>();
        var serviceScope = Substitute.For<IServiceScope>();
        var serviceScopeFactory = Substitute.For<IServiceScopeFactory>();
        serviceScopeFactory.CreateScope().Returns(serviceScope);
        ((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);

        service = new PeriodicBackgroundWorker(provider);
        typeof(PeriodicBackgroundWorker)
            .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)?
            .SetValue(service, TimeSpan.FromMilliseconds(1));

        typeof(PeriodicBackgroundWorker)
            .GetField("executePeriod", BindingFlags.NonPublic | BindingFlags.Instance)?
            .SetValue(service, TimeSpan.FromMilliseconds(1));
    }

    [Fact]
    public async Task WorkRunsTwice_ShouldReturn_WorkCount()
    {
        const int workCount = 2;
        const double periodMs = 100d;

        var period = TimeSpan.FromMilliseconds(periodMs);

        var result = 0;
        Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
        {
            result++;
            return Task.CompletedTask;
        }

        //act        
        var work = Work.CreateByDelegate("", workAction);

        var stopwatch = Stopwatch.StartNew();
        service.Add(work, period);

        var delay = periodMs / 20 + periodMs * workCount - stopwatch.ElapsedMilliseconds;
        await Task.Delay(TimeSpan.FromMilliseconds(delay));

        //assert
        Assert.True(workCount <= result);
    }

    [Fact]
    public async Task Enqueue_Continues_AfterExceptions()
    {
        var expectadResult = 42;
        var result = 0;
        using var semaphore = new SemaphoreSlim(0, 1);

        Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
        {
            result = expectadResult;
            semaphore.Release();
            return Task.CompletedTask;
        }
        var goodWork = Work.CreateByDelegate("", workAction);

        Task failAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
            => throw new Exception();

        var badWork = Work.CreateByDelegate("", failAction);
        badWork.OnErrorAsync = (id, exception, token) => throw new Exception();

        //act
        service.Add(badWork, TimeSpan.FromSeconds(2));
        service.Add(goodWork, TimeSpan.FromSeconds(2));

        await semaphore.WaitAsync(4_100);

        //assert
        Assert.Equal(1, badWork.CountErrors);
        Assert.Equal(1, goodWork.CountComplete);
        Assert.Equal(1, goodWork.CountStart);
        Assert.Equal(expectadResult, result);
    }
}