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

namespace AsbCloudWebApi.Tests.Background;

public class BackgroundWorkerTest
{
    private readonly IServiceProvider serviceProviderMock = Substitute.For<IServiceProvider, ISupportRequiredService>();
    private readonly IServiceScope serviceScopeMock = Substitute.For<IServiceScope>();
    private readonly IServiceScopeFactory serviceScopeFactoryMock = Substitute.For<IServiceScopeFactory>();

    private readonly BackgroundWorker backgroundWorker;

    public BackgroundWorkerTest()
    {
        serviceScopeFactoryMock.CreateScope().Returns(serviceScopeMock);
        ((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock);

        backgroundWorker = new BackgroundWorker(serviceProviderMock);
        typeof(BackgroundWorker)
            .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)
            ?.SetValue(backgroundWorker, TimeSpan.FromMilliseconds(1));
    }

    [Fact]
    public async Task Enqueue_ShouldReturn_WorkCount()
    {
        //arrange
        const int workCount = 10;
        var result = 0;

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

        //act
        for (int i = 0; i < workCount; i++)
        {
            var work = Work.CreateByDelegate(i.ToString(), workAction);
            backgroundWorker.Enqueue(work);
        }

        await backgroundWorker.ExecuteTask!;

        //assert
        Assert.Equal(workCount, result);
    }

    [Fact]
    public async Task Enqueue_Continues_AfterExceptions()
    {
        //arrange
        const int expectadResult = 42;
        var result = 0;

        Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
        {
            result = expectadResult;
            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
        backgroundWorker.Enqueue(badWork);
        backgroundWorker.Enqueue(goodWork);

        await backgroundWorker.ExecuteTask!;

        //assert
        Assert.Equal(expectadResult, result);
        Assert.Equal(1, backgroundWorker.Felled.Count);
        Assert.Equal(1, backgroundWorker.Done.Count);
    }

    [Fact]
    public async Task TryRemoveFromQueue_ShouldReturn_True()
    {
        //arrange
        const int workCount = 5;
        var result = 0;

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

        //act
        for (int i = 0; i < workCount; i++)
        {
            var work = Work.CreateByDelegate(i.ToString(), workAction);
            backgroundWorker.Enqueue(work);
        }

        var removed = backgroundWorker.TryRemoveFromQueue((workCount - 1).ToString());

        await backgroundWorker.ExecuteTask!;

        //assert
        Assert.True(removed);
        Assert.Equal(workCount - 1, result);
        Assert.Equal(workCount - 1, backgroundWorker.Done.Count);
    }
}