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

namespace AsbCloudWebApi.Tests.Services
{
    public class WorkTest
    {
        private IServiceProvider provider;

        public WorkTest()
        {
            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);
        }

        [Fact]
        public async Task Work_done_with_success()
        {
            Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
                => Task.CompletedTask;
            
            var work = Work.CreateByDelegate("", workAction);

            //act
            var begin = DateTime.Now;
            await work.Start(provider, CancellationToken.None);
            var done = DateTime.Now;
            var executionTime = done - begin;

            //assert
            Assert.Equal(1, work.CountComplete);
            Assert.Equal(1, work.CountStart);
            Assert.Equal(0, work.CountErrors);
            Assert.Null(work.CurrentState);
            Assert.Null(work.LastError);

            var lastState = work.LastComplete;
            Assert.NotNull(lastState);
            Assert.InRange(lastState.Start, begin, done - 0.5 * executionTime);
            Assert.InRange(lastState.End, done - 0.5 * executionTime, done);
            Assert.InRange(lastState.ExecutionTime, TimeSpan.Zero, executionTime);
        }

        [Fact]
        public async Task Work_calls_callback()
        {
            var expectedState = "42";
            var expectedProgress = 42d;

            var timeout = TimeSpan.FromMilliseconds(40);

            Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
            {
                callback.Invoke(expectedState, expectedProgress);
                return Task.Delay(timeout);
            }

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

            //act
            var begin = DateTime.Now;
            _ = work.Start(provider, CancellationToken.None);
            await Task.Delay(timeout/3);

            //assert
            Assert.Equal(0, work.CountComplete);
            Assert.Equal(1, work.CountStart);
            Assert.Equal(0, work.CountErrors); 
            Assert.NotNull(work.CurrentState);
            Assert.Null(work.LastComplete);
            Assert.Null(work.LastError);

            var currentState = work.CurrentState;
            Assert.NotNull(currentState);
            Assert.InRange(currentState.Start, begin, begin + timeout);
            Assert.InRange(currentState.StateUpdate, begin, begin + timeout);
            Assert.Equal(expectedState, currentState.State);
            Assert.Equal(expectedProgress, currentState.Progress);
        }


        [Fact]
        public async Task Work_fails_with_info()
        {
            var expectedState = "41";
            var expectedErrorText = "42";
            var minWorkTime = TimeSpan.FromMilliseconds(10);

            async Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
            {
                await Task.Delay(minWorkTime);
                callback(expectedState, 0);
                throw new Exception(expectedErrorText);
            }
            
            var work = Work.CreateByDelegate("", workAction);

            //act
            var begin = DateTime.Now;
            await work.Start(provider, CancellationToken.None);
            
            //assert
            Assert.Equal(0, work.CountComplete);
            Assert.Equal(1, work.CountStart);
            Assert.Equal(1, work.CountErrors);
            Assert.Null(work.CurrentState);
            Assert.Null(work.LastComplete);

            var error = work.LastError;
            Assert.NotNull(error);
            Assert.InRange(error.Start, begin, DateTime.Now);
            Assert.InRange(error.End, begin, DateTime.Now);
            Assert.InRange(error.ExecutionTime, minWorkTime, DateTime.Now - begin);
            Assert.Contains(expectedErrorText, error.ErrorText, StringComparison.InvariantCultureIgnoreCase);
            Assert.Equal(expectedState, error.State);
        }
    }
}