using qdr.fnd.core.pqueue.Configuration;
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 qdr.fnd.core.pqueue.test.Process.Mock;
using Quadarax.Foundation.Core.Logging;
using Quadarax.Foundation.Core.Value.Generators;

namespace qdr.fnd.core.pqueue.test
{
    /// <summary>
    /// Complex test class for <see cref="Mocks.PsQueueMock"/> <see langword="class"/>.
    /// </summary>
    public class PsQueueTest
    {
        #region *** Queue Fields ***    
        private string _queueName;
        private ILogger? _logger;
        private PsQueueConfiguration? _configuration;
        private IPsQueueStorage? _storageProvider;
        #endregion

        #region *** Queue Item Fields ***
        private string _id = "id";
        private string _owner = "owner";
        private string _ownerRef = "ownerRef";
        private string _providerClass = "providerClass";
        private DateTime _created = DateTime.Now;
        private PsStatusEnum _status = PsStatusEnum.New;
        private bool _statusTrancient = false;
        private string _statusMessage = "message";
        private int _attemptsLeft = 3;
        private TinyHash _hash = new TinyHash();
        private IDictionary<string, List<PsStatusEnum>> _ItemStatuses = new Dictionary<string, List<PsStatusEnum>>();
        #endregion

        [SetUp]
        public void Setup()
        {
            _queueName = "queueName";
            _logger = new LoggerMock();
            _configuration = new PsQueueConfiguration();
            _storageProvider = new MemoryStorageProvider(_logger);
            _providerClass = typeof(PsProcessProviderBaseMock).AssemblyQualifiedName!;
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ConstructorOk()
        {
            var queue = CreateQueue();
            Assert.IsFalse(queue.IsRunning);
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ConstructorFail()
        {
            Assert.Throws<ArgumentNullException>(() => new PsQueueMock(string.Empty, _configuration!, _storageProvider!, _logger!));
            Assert.Throws<ArgumentNullException>(() => new PsQueueMock(_queueName, null!, _storageProvider!, _logger!));
            Assert.Throws<ArgumentNullException>(() => new PsQueueMock(_queueName, _configuration!, null!, _logger!));
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ValidateItemIdOk()
        {
            var queue = CreateQueue();
            queue.TestValidateItemId("aaa");
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ValidateItemIdFail()
        {
            var queue = CreateQueue();
            Assert.Throws<ArgumentNullException>(() => queue.TestValidateItemId(string.Empty));
            Assert.Throws<ArgumentNullException>(() => queue.TestValidateItemId(null!));
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ValidateEmitterOk()
        {
            var queue = CreateQueue();
            queue.TestValidateEmitter("aaa");
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ValidateEmitterFail()
        {
            var queue = CreateQueue();
            Assert.Throws<ArgumentNullException>(() => queue.TestValidateEmitter(string.Empty));
            Assert.Throws<ArgumentNullException>(() => queue.TestValidateEmitter(null!));
        }


        [Test(TestOf=typeof(PsQueue))]
        public void ValidateItemOk()
        {
            var queue = CreateQueue();
            var item = CreateQueueItem(_id);
            queue.TestValidateItem(item);
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ValidateItemFail()
        {
            var queue = CreateQueue();
            var item = CreateQueueItem(_id);
            Assert.Throws<ArgumentNullException>(()=>queue.TestValidateItem(null!));

            item.SetOwner(string.Empty, string.Empty);
            var e = Assert.Throws<PSQueueValidationException>(()=>queue.TestValidateItem(item));
            Assert.That(e.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.OwnerRequired));
            
            item.SetOwner(_owner, string.Empty);
            e = Assert.Throws<PSQueueValidationException>(()=>queue.TestValidateItem(item));
            Assert.That(e.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.OwnerRefRequired));
            
            item.SetOwner(_owner, _ownerRef);
            item.ClearArguments();
            Assert.Throws<PsProcessProviderException>(() => queue.TestValidateItem(item));
           
        }

        [Test(TestOf=typeof(PsQueue))]
        public void SetItemStatusInternalOk()
        {
            var queue = CreateQueue();
            var item = CreateQueueItem(_id);
            item.AddToStorage(queue.Storage);


            var testStatus = PsStatusEnum.Pending;
            var initStatus = item.Status;

            Assert.That(item.Status, Is.Not.EqualTo(testStatus));
            queue.TestSetItemStatusInternal(item, testStatus, false, _owner);

            var changedItem = queue.Storage.GetItem(item.Id);
            Assert.That(changedItem.Status, Is.EqualTo(testStatus));
            Assert.That(changedItem.IsStatusTransient, Is.EqualTo(false));
            Assert.That(_ItemStatuses[changedItem.Id].LastOrDefault(), Is.EqualTo(testStatus));
            Assert.That(changedItem.StatusJournal.Count, Is.EqualTo(1));
            Assert.That(changedItem.StatusJournal.FirstOrDefault()?.PreviousStatus, Is.EqualTo(initStatus));

            queue.TestSetItemStatusInternal(item, null, true, _owner);

            changedItem = queue.Storage.GetItem(item.Id);
            Assert.That(changedItem.Status, Is.EqualTo(testStatus));
            Assert.That(changedItem.IsStatusTransient, Is.EqualTo(true));
            Assert.That(_ItemStatuses[changedItem.Id].LastOrDefault(), Is.EqualTo(testStatus));
            Assert.That(changedItem.StatusJournal.Count, Is.EqualTo(2));
            Assert.That(changedItem.StatusJournal.LastOrDefault()?.PreviousIsStatusTransient, Is.EqualTo(false));
            Assert.That(changedItem.StatusJournal.LastOrDefault()?.PreviousStatus, Is.EqualTo(testStatus));

        }

        [Test(TestOf=typeof(PsQueue))]
        public void SetItemStatusInternalFail()
        {
            var queue = CreateQueue();
            var item = CreateQueueItem(_id);
            item.AddToStorage(queue.Storage);


            var testStatus = PsStatusEnum.DoneOk;
            var initStatus = item.Status;

            Assert.That(item.Status, Is.Not.EqualTo(testStatus));
            var e1 = Assert.Throws<PSQueueValidationException>(()=>queue.TestSetItemStatusInternal(item, null, false, _owner));
            Assert.That(e1.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.InvalidStatusTransientCombination));

            e1 = Assert.Throws<PSQueueValidationException>(()=>queue.TestSetItemStatusInternal(item, PsStatusEnum.DoneOk, false, _owner));
            Assert.That(e1.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.InvalidStatusTransition));
            
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ResolveProviderOk()
        {
            var queue = CreateQueue();
            var arguments = PsProcessProviderBaseMock.CreateArguments();
            var provider = queue.TestResolveProvider(_id, _providerClass, arguments);
            Assert.That(provider, Is.Not.Null);
            Assert.That(provider.ItemId, Is.EqualTo(_id));
            Assert.That(provider.IsRunning, Is.EqualTo(false));
            Assert.That(provider.Arguments.Values.Select(x=>x.Name), Is.EquivalentTo(arguments.Select(x=>x.Name)));
            Assert.That(provider.Arguments.Values.Select(x=>x.Value), Is.EquivalentTo(arguments.Select(x=>x.Value)));
            Assert.That(provider.Arguments.Values.Select(x=>x.ValueType), Is.EquivalentTo(arguments.Select(x=>x.ValueType)));
        }

        [Test(TestOf=typeof(PsQueue))]
        public void ResolveProviderFail()
        {
            var queue = CreateQueue();
            var arguments = PsProcessProviderBaseMock.CreateArguments();

            var e1 = Assert.Throws<PSQueueValidationException>(()=>queue.TestResolveProvider(_id, null!, arguments));
            Assert.That(e1.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.ProcessProviderRequired));
            e1 = Assert.Throws<PSQueueValidationException>(()=>queue.TestResolveProvider(_id, string.Empty, arguments));
            Assert.That(e1.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.ProcessProviderRequired));

            e1 = Assert.Throws<PSQueueValidationException>(()=>queue.TestResolveProvider(string.Empty, "aaa", arguments));
            Assert.That(e1.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.ProcessProviderCannotResolve));

            e1 = Assert.Throws<PSQueueValidationException>(()=>queue.TestResolveProvider(string.Empty, _providerClass, arguments));
            Assert.That(e1.Code, Is.EqualTo(PSQueueValidationException.ErrorCodes.ProcessProviderCannotResolve));

        }

        [Test(TestOf=typeof(PsQueue))]
        public void EnsureItemStateExpiredOk()
        {
            var queue = CreateQueue();
            var item = CreateQueueItem(_id);
            
            // no expiration
            item.SetValidity(DateTime.Now.AddSeconds(-10), DateTime.Now.AddSeconds(100));
            item.SetStatus(PsStatusEnum.Pending, false, "noway");
            item.AddToStorage(queue.Storage);

            queue.TestEnsureItemStateExpired(item);
            var changedItem = queue.Storage.GetItem(item.Id);
            Assert.That(changedItem.Status, Is.EqualTo(PsStatusEnum.Pending));
            Assert.That(changedItem.IsStatusTransient, Is.EqualTo(false));
            
        }

        private PsQueueMock CreateQueue()
        {
            var ps = new PsQueueMock(_queueName, _configuration!, _storageProvider!, _logger!);
            ps.ProcessItemStatusChanged += OnProcessItemStatusChanged;
            return ps;
        }

        private void OnProcessItemStatusChanged(IPsQueue sender, PsQueueStatusChangedEventArgs e)
        {
            TestContext.WriteLine($"#StaChg: ItemId={e.ProcessQueueItemId}; newStatus={e.CurrentStatus}; isTransient='{e.CurrentIsStatusTransient}'");
            if (_ItemStatuses.ContainsKey(e.ProcessQueueItemId))
                _ItemStatuses[e.ProcessQueueItemId].Add(e.CurrentStatus);
            else
                _ItemStatuses.Add(e.ProcessQueueItemId, new List<PsStatusEnum> { e.CurrentStatus });
        }

        private PsQueueItemMock CreateQueueItem(string id)
        {
            var item = new PsQueueItemMock(id, _owner,_ownerRef,_providerClass,_created, PsProcessProviderBaseMock.CreateArguments(), _attemptsLeft);
            return item;
        }
    }
}
