using qdr.fnd.core.pqueue.Enums;
using qdr.fnd.core.pqueue.Exceptions;
using qdr.fnd.core.pqueue.Storages;
using qdr.fnd.core.pqueue.test.Mocks;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Thread;
using Quadarax.Foundation.Core.Value.Extensions;
using Quadarax.Foundation.Core.Value.Generators;

namespace qdr.fnd.core.pqueue.test.Storages
{
    public class MemoryStorageProviderTest
    {
        private LoggerMock _logger;


        private string _owner = "owner";
        private string _ownerRef = "ownerRef";
        private string _providerClass = "providerClass";
        private int _initialAttempts = 10;
        private DateTime _created = DateTime.Now;

        [SetUp]
        public void Setup()
        {
            _logger = new LoggerMock();
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void ConstructorOk()
        {
            var provider = new MemoryStorageProvider(_logger);
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void CreateItemOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            Assert.That(string.IsNullOrEmpty(itemId), Is.False);
        }
        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void ContainsItemOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            Assert.That(provider.ContainsItem(itemId), Is.True);
            Assert.That(provider.ContainsItem("not-exist"), Is.False);
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void DeleteItemOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            provider.DeleteItem(itemId);
            Assert.That(provider.ContainsItem(itemId), Is.False);
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void DeleteItemFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            provider.DeleteItem(itemId);
            Assert.Throws<ArgumentNullException>(() => provider.DeleteItem(string.Empty));
            var psEx = Assert.Throws<PSStorageException>(() => provider.DeleteItem(itemId));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void UpdateItemPriorityOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var priority = 99;
            var item = provider.GetItem(itemId);
            Assert.That(item.Priority, Is.Not.EqualTo(priority));

            provider.UpdateItemPriority(itemId, priority);

            item = provider.GetItem(itemId);
            Assert.That(item.Priority, Is.EqualTo(priority));
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void UpdateItemPriorityFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            Assert.Throws<ArgumentNullException>(() => provider.UpdateItemPriority(string.Empty,0));
            var psEx = Assert.Throws<PSStorageException>(() => provider.DeleteItem("not-exists"));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void UpdateItemValidityOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var validFrom = DateTime.Now;
            var validTo = validFrom.AddHours(1);
            var item = provider.GetItem(itemId);
            Assert.That(item.ValidFrom, Is.Not.EqualTo(validFrom));
            Assert.That(item.ValidTo, Is.Not.EqualTo(validTo));

            provider.UpdateItemValidity(itemId, validFrom, validTo);

            item = provider.GetItem(itemId);
            Assert.That(item.ValidFrom, Is.EqualTo(validFrom));
            Assert.That(item.ValidTo, Is.EqualTo(validTo));

            provider.UpdateItemValidity(itemId, validFrom, null);
            Assert.That(item.ValidTo, Is.Null);
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void UpdateItemValidityFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            Assert.Throws<ArgumentNullException>(() => provider.UpdateItemValidity(string.Empty,DateTime.Now, DateTime.Now));
            var psEx = Assert.Throws<PSStorageException>(() => provider.UpdateItemValidity("not-exists",DateTime.Now, DateTime.Now));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));
        }
        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void GetItemFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            Assert.Throws<ArgumentNullException>(() => provider.GetItem(string.Empty));
            var psEx = Assert.Throws<PSStorageException>(() => provider.GetItem("not-exists"));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void UpdateItemStatusOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var status = PsStatusEnum.DoneOk;
            var transient = true;
            var item = provider.GetItem(itemId);
            Assert.That(item.Status, Is.Not.EqualTo(status));
            Assert.That(item.IsStatusTransient, Is.Not.EqualTo(transient));

            provider.UpdateItemStatus(itemId, status, transient);

            item = provider.GetItem(itemId);
            Assert.That(item.Status, Is.EqualTo(status));
            Assert.That(item.IsStatusTransient, Is.EqualTo(transient));

            provider.UpdateItemStatus(itemId, null, !transient);
            item = provider.GetItem(itemId);
            Assert.That(item.Status, Is.EqualTo(status));
            Assert.That(item.IsStatusTransient, Is.EqualTo(!transient));
          
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void UpdateItemStatusFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            Assert.Throws<ArgumentNullException>(() => provider.UpdateItemStatus(string.Empty,null, true));
            var psEx = Assert.Throws<PSStorageException>(() => provider.UpdateItemStatus("not-exists",null, true));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void UpdateItemStartedOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var started = DateTime.Now;
            var item = provider.GetItem(itemId);
            Assert.That(item.Started, Is.Not.EqualTo(started));

            provider.UpdateItemStarted(itemId, started);

            item = provider.GetItem(itemId);
            Assert.That(item.Started, Is.EqualTo(started));

            provider.UpdateItemStarted(itemId, null);
            item = provider.GetItem(itemId);
            Assert.That(item.Started, Is.Null);
          
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void UpdateItemStartedFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            Assert.Throws<ArgumentNullException>(() => provider.UpdateItemStarted(string.Empty,null));
            var psEx = Assert.Throws<PSStorageException>(() => provider.UpdateItemStarted("not-exists",null));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void UpdateItemFinishedOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var finished = DateTime.Now;
            var item = provider.GetItem(itemId);
            Assert.That(item.Finished, Is.Not.EqualTo(finished));

            provider.UpdateItemFinished(itemId, finished);

            item = provider.GetItem(itemId);
            Assert.That(item.Finished, Is.EqualTo(finished));

            provider.UpdateItemFinished(itemId, null);
            item = provider.GetItem(itemId);
            Assert.That(item.Finished, Is.Null);
          
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void UpdateItemFinishedFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            Assert.Throws<ArgumentNullException>(() => provider.UpdateItemFinished(string.Empty,null));
            var psEx = Assert.Throws<PSStorageException>(() => provider.UpdateItemFinished("not-exists",null));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void CreateItemAttributeOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var typeClass = "System.String";
            var value = "valueXXX";
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var item = provider.GetItem(itemId);
            Assert.That(item.Arguments.Count(), Is.EqualTo(0));

            provider.CreateItemAttribute(itemId, _owner, value,typeClass,true);
            item = provider.GetItem(itemId);

            Assert.That(item.Arguments.FirstOrDefault(x=>x.Name == _owner), Is.Not.Null);
            var attr = item.Arguments.First(x=>x.Name == _owner);
            Assert.That(attr.ValueType.FullName, Is.EqualTo(Type.GetType(typeClass)?.FullName));
            Assert.That(attr.IsOutput, Is.EqualTo(true));
          
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void CreateItemAttributeFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            var typeClass = "System.String";
            var value = "valueXXX";

            Assert.Throws<ArgumentNullException>(() => provider.CreateItemAttribute(string.Empty,_owner,typeClass, value, true));
            var psEx = Assert.Throws<PSStorageException>(() => provider.CreateItemAttribute("non=exists",_owner,typeClass, value, true));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemNotFound));

            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            provider.CreateItemAttribute(itemId, _owner, value,typeClass,true); 
            psEx = Assert.Throws<PSStorageException>(() => provider.CreateItemAttribute(itemId,_owner,typeClass, value, true));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemArgumentAlreadyExists));

            Assert.Throws<ArgumentNullException>(() => provider.CreateItemAttribute(itemId,string.Empty,typeClass, value, true));
            Assert.Throws<ArgumentNullException>(() => provider.CreateItemAttribute(itemId, null!, typeClass, value, true));
            Assert.Throws<ArgumentNullException>(() => provider.CreateItemAttribute(itemId,"non-exists",string.Empty, value, true));
            Assert.Throws<ArgumentNullException>(() => provider.CreateItemAttribute(itemId, "non-exists", null!, value, true));
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void UpdateItemAttributeOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var typeClass = "System.String";
            var value = "valueXXX";
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var item = provider.GetItem(itemId);
            Assert.That(item.Arguments.Count(), Is.EqualTo(0));

            provider.CreateItemAttribute(itemId, _owner, value,typeClass,true);
            item = provider.GetItem(itemId);
            var attr = item.Arguments.FirstOrDefault(x=>x.Name == _owner);
            Assert.That(attr, Is.Not.Null);
            provider.UpdateItemAttribute(itemId, _owner, value + "1");
            item = provider.GetItem(itemId);
            attr = item.Arguments.FirstOrDefault(x=>x.Name == _owner);
            Assert.That(attr, Is.Not.Null);
            Assert.That(attr.Value, Is.EqualTo(value + "1"));
          
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void UpdateItemAttributeFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            var typeClass = "System.String";

            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var psEx = Assert.Throws<PSStorageException>(() => provider.UpdateItemAttribute(itemId,_owner,typeClass));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemArgumentNotFound));

            Assert.Throws<ArgumentNullException>(() => provider.UpdateItemAttribute(itemId,string.Empty,typeClass));
            Assert.Throws<ArgumentNullException>(() => provider.UpdateItemAttribute(itemId, null!, typeClass));
           
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void DeleteItemAttributeOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var typeClass = "System.String";
            var value = "valueXXX";
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var item = provider.GetItem(itemId);
            Assert.That(item.Arguments.Count(), Is.EqualTo(0));

            provider.CreateItemAttribute(itemId, _owner, value,typeClass,true);
            item = provider.GetItem(itemId);
            var attr = item.Arguments.FirstOrDefault(x=>x.Name == _owner);
            Assert.That(attr, Is.Not.Null);
            provider.DeleteItemAttribute(itemId, _owner);
            item = provider.GetItem(itemId);
            Assert.That(item.Arguments.Count(), Is.EqualTo(0));
          
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void DeleteItemAttributeFail()
        {
            var provider = new MemoryStorageProvider(_logger);

            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var psEx = Assert.Throws<PSStorageException>(() => provider.DeleteItemAttribute(itemId,_owner));
            Assert.That(psEx, Is.Not.Null);
            Assert.That(psEx.Code, Is.EqualTo(PSStorageException.ErrorCodes.PsStorageItemArgumentNotFound));

            Assert.Throws<ArgumentNullException>(() => provider.DeleteItemAttribute(itemId,string.Empty));
            Assert.Throws<ArgumentNullException>(() => provider.DeleteItemAttribute(itemId, null!));
           
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void AppendLogOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var value = "valueXXX";
            var code = -1;
            ErrorLevelEnum level = ErrorLevelEnum.Error;
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var item = provider.GetItem(itemId);
            Assert.That(item.Log.Count(), Is.EqualTo(0));

            provider.AppendLog(itemId, value, code, level);
            item = provider.GetItem(itemId);
            var logItem = item.Log.FirstOrDefault(x=>x.Message == value);
            
            Assert.That(logItem, Is.Not.Null);
            Assert.That(logItem.Code, Is.EqualTo(code));
            Assert.That(logItem.Level, Is.EqualTo(level));
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void AppendLogFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
           
            Assert.Throws<ArgumentNullException>(() => provider.AppendLog(itemId,string.Empty, 0,ErrorLevelEnum.Error));
            Assert.Throws<ArgumentNullException>(() => provider.AppendLog(itemId,null!, 0,ErrorLevelEnum.Error));
           
        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void AppendStatusJournalOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var value = "valueXXX";
            PsStatusEnum status = PsStatusEnum.DoneOk;
            var isTransient = true;
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
            var item = provider.GetItem(itemId);
            Assert.That(item.StatusJournal.Count(), Is.EqualTo(0));

            provider.AppendStatusJournal(itemId, status, isTransient, value);
            item = provider.GetItem(itemId);
            var journal = item.StatusJournal.FirstOrDefault(x=>x.Description == value);
            
            Assert.That(journal, Is.Not.Null);
            Assert.That(journal.PreviousStatus, Is.EqualTo(status));
            Assert.That(journal.PreviousIsStatusTransient, Is.EqualTo(isTransient));
        }

        [Test(TestOf = typeof(MemoryStorageProvider))]
        public void AppendStatusJournalFail()
        {
            var provider = new MemoryStorageProvider(_logger);
            var itemId = provider.CreateItem(_owner, _ownerRef, _providerClass, _initialAttempts, _created);
           // no further tests needed
        }


        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void QueryOk()
        {
            var provider = new MemoryStorageProvider(_logger);
            var status1 = PsStatusEnum.DoneOk;
            var status2 = PsStatusEnum.DoneFailed;
            var itemId1 = provider.CreateItem(_owner+"1", _ownerRef+"1", _providerClass, _initialAttempts, _created);
            var itemId2 = provider.CreateItem(_owner+"2", _ownerRef+"2", _providerClass, _initialAttempts, _created);
            var itemId3 = provider.CreateItem(_owner+"2", _ownerRef+"3", _providerClass, _initialAttempts, _created);

            provider.UpdateItemStatus(itemId1, status1, true);
            provider.UpdateItemStatus(itemId2, status2, true);
            provider.UpdateItemStatus(itemId3, status2, false);

            var result = provider.Query(_owner+"1", null).ToList();
            Assert.That(result.Count, Is.EqualTo(1));
            result = provider.Query(_owner+"2", null).ToList();
            Assert.That(result.Count, Is.EqualTo(2));
            result = provider.Query(null, _ownerRef+"3").ToList();
            Assert.That(result.Count, Is.EqualTo(1));
            result = provider.Query(null, null, new Tuple<bool, PsStatusEnum>(true,status1), new Tuple<bool, PsStatusEnum>(true, status2)).ToList();
            Assert.That(result.Count, Is.EqualTo(2));


        }

        [Test(TestOf=typeof(MemoryStorageProvider))]
        public void DurabilityTest()
        {
            var provider = new MemoryStorageProvider(_logger);
            var moshpit = new MoshPit<MoshPitContext>(new MoshPitContext(provider));
            moshpit.AddAction((ctx) =>
            {
                var itemId = ctx.Provider.CreateItem(ctx.HashGenerator.NewId(), ctx.HashGenerator.NewId(), ctx.HashGenerator.NewId(), 10, DateTime.Now);
                ctx.Provider.AppendLog(itemId, "log", 0, ErrorLevelEnum.Error);
                ctx.Provider.AppendStatusJournal(itemId, PsStatusEnum.DoneOk, true, "journal");
                ctx.Identities.Add(itemId);
            });
            moshpit.AddAction((ctx) =>
            {
                if (ctx.Identities.Count==0)
                    return;

                var itemId = ctx.Identities.GetRandom();
                ctx.Identities.Remove(itemId);
                ctx.Provider.DeleteItem(itemId);
            });
            moshpit.AddAction((ctx) =>
            {
                if (ctx.Identities.Count==0)
                    return;

                var itemId = ctx.Identities.GetRandom();
                ctx.Identities.Remove(itemId);
                ctx.Provider.DeleteItem(itemId);
            });
            moshpit.AddAction((ctx) =>
            {
                if (ctx.Identities.Count==0)
                    return;

                var itemId = ctx.Identities.GetRandom();
                ctx.Provider.UpdateItemStatus(itemId, PsStatusEnum.DoneOk,false);
            });
            moshpit.Start(100, 10000);
            TestContext.WriteLine($"Total duration: {moshpit.TotalDuration}");
            TestContext.WriteLine($"Total iterations: {moshpit.Iterations}");
            TestContext.WriteLine($"AVG iteration duration: {moshpit.AvgIterationDuration}");
            TestContext.WriteLine($"AVG iterations per second: {moshpit.AvgIterationPerSecond}");
        }

        private class MoshPitContext
        {
            public MemoryStorageProvider Provider { get;}
            public List<string> Identities { get; }
            public TinyHash HashGenerator { get; }

            public MoshPitContext(MemoryStorageProvider provider)
            {
                Provider = provider;
                Identities = new List<string>();
                HashGenerator = new TinyHash(5);
            }
        }
    }
}
