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.UnitTests.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);
	}
}