using qdr.fnd.core.pqueue.Enums;
using qdr.fnd.core.pqueue.Exceptions;
using qdr.fnd.core.pqueue.fnd.candidates;
using qdr.fnd.core.pqueue.Process;
using qdr.fnd.core.pqueue.test.Mocks;
using qdr.fnd.core.pqueue.test.Process.Mock;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Value.Extensions;
using System.Collections.Concurrent;

namespace qdr.fnd.core.pqueue.test.Process
{
    internal class PsProcessProviderBaseTest
    {

        private IDictionary<string, List<string>> _stdOutPool;
        private IDictionary<string, List<Tuple<PsStatusEnum, string>>> _statusDonePool;
        
        [SetUp]
        public void Setup()
        {
            _stdOutPool = new ConcurrentDictionary<string, List<string>>();
            _statusDonePool = new ConcurrentDictionary<string, List<Tuple<PsStatusEnum, string>>>();
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void ConstructorOk()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            var process = CreateProcess(args);
            Assert.IsFalse(process.IsRunning);
            Assert.That(args, Is.EquivalentTo(process.Arguments.Values));
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void ConstructorFail()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            var itemId = "a";
            Assert.Throws<ArgumentNullException>(() => new PsProcessProviderBaseMock(new PsProcessProviderBase.Callbacks(null!, OnProcessStdOut),itemId, args, new LoggerMock()));
            Assert.Throws<ArgumentNullException>(() => new PsProcessProviderBaseMock(new PsProcessProviderBase.Callbacks(OnProcessDone, null!),itemId, args, new LoggerMock()));
            Assert.Throws<ArgumentNullException>(() => new PsProcessProviderBaseMock(new PsProcessProviderBase.Callbacks(OnProcessDone, OnProcessStdOut),null!, args, new LoggerMock()));
            Assert.Throws<ArgumentNullException>(() => new PsProcessProviderBaseMock(new PsProcessProviderBase.Callbacks(OnProcessDone, OnProcessStdOut),string.Empty, args, new LoggerMock()));
            Assert.Throws<ArgumentNullException>(() => new PsProcessProviderBaseMock(new PsProcessProviderBase.Callbacks(OnProcessDone, OnProcessStdOut),itemId, null!, new LoggerMock()));
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void OnProcessErrorOk()
        {
            var testMessage = "Test";
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            var process = CreateProcess(args);
            process.TestOnProcessError(new Exception(testMessage));
            Assert.That(_stdOutPool.ContainsKey(process.ItemId), Is.True);
            Assert.That(_stdOutPool[process.ItemId].Contains(testMessage), Is.True);
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void OnProcessErrorFail()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            var process = CreateProcess(args);

            var ex = Assert.Throws<AggregateException>(()=>process.TestOnProcessError(null!));
            Assert.That(ex.InnerExceptions.Any(e => e is ArgumentNullException), Is.True);
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void GetArgumentValueOk()
        {
            var value = "a";
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            args.Where(x=>x.Name == PsProcessProviderBase.CS_ARG_RESULT_TEXT).First().Value = value;
            var process = CreateProcess(args);
            Assert.That(process.TestGetArgumentValue<string>(PsProcessProviderBase.CS_ARG_RESULT_TEXT), Is.EqualTo(value));
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void GetArgumentValueFail()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            var process = CreateProcess(args);
            Assert.Throws<ArgumentNullException>(() => process.TestGetArgumentValue<string>(null!));
            Assert.Throws<ArgumentNullException>(() => process.TestGetArgumentValue<string>(string.Empty));
            Assert.That(process.TestGetArgumentValue<string>("a"), Is.Null);
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void ContainsArgumentOk()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            var process = CreateProcess(args);
            Assert.That(process.TestContainsArgument(PsProcessProviderBase.CS_ARG_RESULT_TEXT), Is.True);
            Assert.That(process.TestContainsArgument("a"), Is.False);
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void ContainsArgumentFail()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            var process = CreateProcess(args);
            Assert.Throws<ArgumentNullException>(() => process.TestContainsArgument(string.Empty));
            Assert.Throws<ArgumentNullException>(() => process.TestContainsArgument(null!));
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void StartOk()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            args.SetArgumentValue<TimeSpan>(PsProcessProviderBase.CS_ARG_PROCESS_TIMEOUT, TimeSpan.FromSeconds(5));

            var process = CreateProcess(args);
            var dtStart = DateTime.Now;
            process.Start();
            process.WaitToDone();
            TestContext.WriteLine($"#Process duration check: {(DateTime.Now - dtStart).ToReadableString()}");

            Assert.That(process.IsRunning, Is.False);
            Assert.That(_statusDonePool.ContainsKey(process.ItemId), Is.True);
            Assert.That(_statusDonePool[process.ItemId].Any(x => x.Item1 == PsStatusEnum.DoneOk), Is.True);
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void StartFail()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            args.SetArgumentValue<TimeSpan>(PsProcessProviderBase.CS_ARG_PROCESS_TIMEOUT, TimeSpan.FromSeconds(15));

            var process = CreateProcess(args);
            var dtStart = DateTime.Now;
            process.Start();
            Assert.That(process.IsRunning, Is.True);
            var e = Assert.Throws<PsProcessProviderException>(()=>process.Start());
            process.Stop();
            Assert.That(e.Code, Is.EqualTo(PsProcessProviderException.ErrorCodes.ProcessProviderIsRunning));
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void StopFail()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            args.SetArgumentValue<TimeSpan>(PsProcessProviderBase.CS_ARG_PROCESS_TIMEOUT, TimeSpan.FromSeconds(5));

            var process = CreateProcess(args);
            Assert.That(process.IsRunning, Is.False);
            var e = Assert.Throws<PsProcessProviderException>(()=>process.Stop());
            Assert.That(e.Code, Is.EqualTo(PsProcessProviderException.ErrorCodes.ProcessProviderNotRunning));
        }



        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void StartStopOk()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            args.SetArgumentValue<TimeSpan>(PsProcessProviderBase.CS_ARG_PROCESS_TIMEOUT, TimeSpan.FromSeconds(5));

            var process = CreateProcess(args);
            var dtStart = DateTime.Now;
            process.Start();
            Task.Delay(TimeSpan.FromSeconds(1)).Wait();
            process.Stop();
            process.WaitToDone();
            TestContext.WriteLine($"#Process duration check: {(DateTime.Now - dtStart).ToReadableString()}");

            Assert.That(process.IsRunning, Is.False);
            Assert.That(_statusDonePool.ContainsKey(process.ItemId), Is.True);
            Assert.That(_statusDonePool[process.ItemId].Any(x => x.Item1 == PsStatusEnum.DoneFailed), Is.True);
        }



        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void StartTimeoutFail()
        {
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            args.SetArgumentValue<TimeSpan>(PsProcessProviderBase.CS_ARG_PROCESS_TIMEOUT, TimeSpan.FromSeconds(1));

            var process = CreateProcess(args);
            var dtStart = DateTime.Now;
            process.Start();
            process.WaitToDone();
            TestContext.WriteLine($"#Process duration check: {(DateTime.Now - dtStart).ToReadableString()}");
            
            Assert.That(process.IsRunning, Is.False);
            Assert.That((DateTime.Now - dtStart).TotalSeconds, Is.LessThan(2), "Timeout restriction doesn't work.");
            Assert.That(_statusDonePool.ContainsKey(process.ItemId), Is.True);
            Assert.That(_statusDonePool[process.ItemId].Any(x => x.Item1 == PsStatusEnum.DoneFailed), Is.True);
        }

        [Test(TestOf=typeof(PsProcessProviderBase))]
        public void ProcessFailedInsideOk()
        {
            var inProcessMessage = "Foo wants to live";
            var args = PsProcessProviderBaseMock.CreateArguments().ToArray();
            args.SetArgumentValue<TimeSpan>(PsProcessProviderBase.CS_ARG_PROCESS_TIMEOUT, TimeSpan.FromSeconds(500));
            args.SetArgumentValue<string>(PsProcessProviderBaseMock.CS_ARG_EMIT_EXC, inProcessMessage);


            var process = CreateProcess(args);
            var dtStart = DateTime.Now;
            process.Start();
            process.WaitToDone();
            TestContext.WriteLine($"#Process duration check: {(DateTime.Now - dtStart).ToReadableString()}");

            Assert.That(process.IsRunning, Is.False);
            Assert.That(_statusDonePool.ContainsKey(process.ItemId), Is.True);
            Assert.That(_statusDonePool[process.ItemId].Any(x => x.Item1 == PsStatusEnum.DoneFailed && x.Item2.Contains(inProcessMessage)), Is.True);
        }

        private PsProcessProviderBaseMock CreateProcess(TypedArgument[] args)
        {
            var itemId = "a";
            return new PsProcessProviderBaseMock(new PsProcessProviderBase.Callbacks(OnProcessDone, OnProcessStdOut), itemId, args, new LoggerMock());
        }

        private void OnProcessStdOut(string itemId, string message)
        {
            TestContext.WriteLine($"#StdOut: ItemId={itemId}; message='{message}'");
            _stdOutPool.TryAdd(itemId, new List<string>());
            _stdOutPool[itemId].Add(message);
        }

        private void OnProcessDone(string itemId, PsStatusEnum newStatus, string message)
        {
            TestContext.WriteLine($"#StaChg: ItemId={itemId}; newStatus={newStatus}; message='{message}'");
            _statusDonePool.TryAdd(itemId, new List<Tuple<PsStatusEnum, string>>());
            _statusDonePool[itemId].Add(new Tuple<PsStatusEnum, string>(newStatus, message));
        }
    }
}
