using qdr.fnd.core.pqueue.Enums;
using qdr.fnd.core.pqueue.Exceptions;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Logging;
using Quadarax.Foundation.Core.Object;
using Quadarax.Foundation.Core.Value.Generators;
using System.Xml.Linq;

namespace qdr.fnd.core.pqueue.Storages
{
    public abstract class BaseStorageProvider : DisposableObject, IPsQueueStorage
    {
        
        #region *** Properties ***
        protected ILog? Log => _log;
        #endregion

        #region *** Private Fields ***

        private ILog? _log;
        private IIdGenerator<string> _tranGenerator;

        #endregion

        #region *** Constructors ***
        protected BaseStorageProvider(ILogger? logger)
        {
            _log = logger?.GetLogger(GetType().Name);
            _tranGenerator = new TinyHash(10);
        }

        #endregion


        #region Implementation of IPsQueueStorage

        /// <inheritdoc />
        public string CreateItem(string owner, string ownerRef, string providerClass, int initialAttempts, DateTime? created)
        {
            if (string.IsNullOrWhiteSpace(owner))
                throw new ArgumentNullException(nameof(owner));
            if (string.IsNullOrWhiteSpace(ownerRef))
                throw new ArgumentNullException(nameof(ownerRef));
            if (string.IsNullOrWhiteSpace(providerClass))
                throw new ArgumentNullException(nameof(providerClass));
            if (created == null)
                created = DateTime.Now;

            var id = _tranGenerator.NewId();

            var newId = OnCreateItem(id, owner, ownerRef, providerClass, initialAttempts, created.Value, id);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={id}, owner={owner}, ownerRef{ownerRef}] created.");
            return newId;
        }

        protected abstract string OnCreateItem(string id, string owner, string ownerRef, string providerClass, int initialAttempts, DateTime created, string s);

        /// <inheritdoc />
        public void DeleteItem(string itemId)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            OnDeleteItem(itemId);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] deleted.");
        }

        protected abstract void OnDeleteItem(string itemId);

        /// <inheritdoc />
        public void UpdateItemPriority(string itemId, int priority)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            OnUpdateItemPriority(itemId, priority);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] priority[{priority}] updated.");
        }
        protected abstract void OnUpdateItemPriority(string itemId, int priority);

        /// <inheritdoc />
        public void UpdateItemValidity(string itemId, DateTime validForm, DateTime? validTo)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            OnUpdateItemValidity(itemId, validForm, validTo);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] validFrom[{validForm}], validTo[{(validTo == null ? "NULL" : validTo.ToString())}] updated.");
        }

        protected abstract void OnUpdateItemValidity(string itemId, DateTime validForm, DateTime? validTo);

        /// <inheritdoc />
        public void UpdateItemStatus(string itemId, PsStatusEnum? status, bool isTransient)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            OnUpdateItemStatus(itemId, status, isTransient);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] isTransient[{isTransient}], status[{(status == null ? "NULL" : status.ToString())}] updated.");
        }

        protected abstract void OnUpdateItemStatus(string itemId, PsStatusEnum? status, bool isTransient);

        /// <inheritdoc />
        public void UpdateItemAttempts(string itemId, int attemptsLeft)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            OnUpdateItemAttempts(itemId, attemptsLeft);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] attemptsLeft[{attemptsLeft}] updated.");
        }

        protected abstract void OnUpdateItemAttempts(string itemId, int attemptsLeft);

        /// <inheritdoc />
        public void UpdateItemStarted(string itemId, DateTime? started)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            OnUpdateItemStarted(itemId, started);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] started[{(started == null ? "NULL" : started.ToString())}] updated.");
        }

        protected abstract void OnUpdateItemStarted(string itemId, DateTime? started);

        /// <inheritdoc />
        public void UpdateItemFinished(string itemId, DateTime? finished)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            OnUpdateItemFinished(itemId, finished);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] finished[{(finished == null ? "NULL" : finished.ToString())}] updated.");
        }

        protected abstract void OnUpdateItemFinished(string itemId, DateTime? finished);

        /// <inheritdoc />
        public void CreateItemAttribute(string itemId, string name, string? value, string valueTypeClass, bool isOutput)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
           
            var item = GetItem(itemId);
            if (item.Arguments.Any(x=>string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase)))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemArgumentAlreadyExists, itemId, name);

            OnCreateItemAttribute(itemId, name, value, valueTypeClass, isOutput);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] argument name[{name}], value[{value}], valueType[{valueTypeClass}] updated.");
        
        }

        protected abstract void OnCreateItemAttribute(string itemId, string name, string? value, string valueTypeClass, bool isOutput);

        /// <inheritdoc />
        public void UpdateItemAttribute(string itemId, string name, string value)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));
           
            var item = GetItem(itemId);
            if (!item.Arguments.Any(x=>string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase)))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemArgumentNotFound, itemId, name);

            OnUpdateItemAttribute(itemId, name, value);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] argument name[{name}], value[{value}] updated.");

        }

        protected abstract void OnUpdateItemAttribute(string itemId, string name, string value);

        /// <inheritdoc />
        public void DeleteItemAttribute(string itemId, string name)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentNullException(nameof(name));
           
            var item = GetItem(itemId);
            if (!item.Arguments.Any(x=>string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase)))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemArgumentNotFound, itemId, name);

            OnDeleteItemAttribute(itemId, name);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] argument name[{name}] deleted.");

        }


        protected abstract void OnDeleteItemAttribute(string itemId, string name);

        /// <inheritdoc />
        public void AppendLog(string itemId, string message, int code, ErrorLevelEnum messageType)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (string.IsNullOrWhiteSpace(message))
                throw new ArgumentNullException(nameof(message));
           
            var item = GetItem(itemId);
            
            OnAppendLog(itemId, message, code, messageType);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] log message[{message}], code[{code}], messageType[{messageType}] appended.");

        }

        protected abstract void OnAppendLog(string itemId, string message, int code, ErrorLevelEnum messageType);

        /// <inheritdoc />
        public void AppendStatusJournal(string itemId, PsStatusEnum status, bool isTransient, string message)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
           
            var item = GetItem(itemId);
            
            OnAppendStatusJournal(itemId, status, isTransient, message);

            _log?.Log(LogSeverityEnum.Trace, $"Item [id={itemId}] log message[{message}], status[{status}], isTransient[{isTransient}] appended.");

        }

        protected abstract void OnAppendStatusJournal(string itemId, PsStatusEnum status, bool isTransient, string message);

        /// <inheritdoc />
        public IQueryable<IPsQueueItem> Query(string? owner, string? ownerRef, params Tuple<bool, PsStatusEnum>[] status)
        {
            var query = OnQuery();
            if (status.Length>0)
                query = query.Where(x => status.Any(s => s.Item1 == x.IsStatusTransient && s.Item2 == x.Status));
            if (!string.IsNullOrWhiteSpace(owner))
                query = query.Where(x => string.Equals(x.Owner, owner, StringComparison.InvariantCulture));
            if (!string.IsNullOrWhiteSpace(ownerRef))
                query = query.Where(x => string.Equals(x.OwnerRef, ownerRef, StringComparison.InvariantCulture));

            owner = owner ?? "NULL";
            ownerRef = ownerRef ?? "NULL";
            var statuses = string.Join(",", status.Select(x => $"[IsTransient:{x.Item1},Status:{x.Item2}]"));
            _log?.Log(LogSeverityEnum.Trace, $"Query [owner={owner}], ownerRef[{ownerRef}], statuses[{statuses}] executed.");

            return query;
        }

        protected abstract IQueryable<IPsQueueItem> OnQuery();

        /// <inheritdoc />
        public IPsQueueItem GetItem(string itemId)
        {
            if (string.IsNullOrWhiteSpace(itemId))
                throw new ArgumentNullException(nameof(itemId));
            if (!ContainsItem(itemId))
                throw new PSStorageException(PSStorageException.ErrorCodes.PsStorageItemNotFound, itemId);

            return OnGetItem(itemId);
        }

        protected abstract IPsQueueItem OnGetItem(string itemId);

        /// <inheritdoc />
        public abstract bool ContainsItem(string itemId);

        #endregion
    }
}
