
using Quadarax.Foundation.Core.Business.Processor.Enums;
using Quadarax.Foundation.Core.Business.Processor.Task;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Logging;
using Quadarax.Foundation.Core.Object;

namespace Quadarax.Foundation.Core.Business.Processor.Providers;
public abstract class ProcessorStorageProvider : DisposableObject, IProcessorStorageProvider
{
    #region *** Properties ***
    public bool IsOpen { get; private set; }
    #endregion

    #region *** Fields ***
    private readonly ILog _log;
    #endregion

    #region *** Constructors ***

    protected ProcessorStorageProvider()
    {
        _log = new ConsoleLogger().GetLogger(GetType());
    }
    #endregion

    public void Open()
    {
        if (IsOpen) throw new InvalidOperationException("Storage provider has been already opened.");

        OnOpen();

        IsOpen = true;
        Log(LogSeverityEnum.Debug, "Storage provider has been opened.");
    }
    
    public void Close()
    {
        if (!IsOpen) throw new InvalidOperationException("Storage provider has been already closed.");

        OnClose();

        IsOpen = false;
        Log(LogSeverityEnum.Debug, "Storage provider has been closed.");
    }

    public bool EnsureStorage()
    {
        var result = OnEnsureStorage();
        Log(LogSeverityEnum.Debug, $"Storage provider has been ensured with result '{result}'.");
        return result;
    }

    public ITaskIdentifier Create(ITaskItemData item)
    {
        if (item == null) throw new ArgumentNullException(nameof(item));
        EnsureOpen();
        var result = OnCreate(item);
        Log(LogSeverityEnum.Debug, $"Task item '{result}' has been created in storage.");
        return result;
    }

    public ITaskItemData[] Get(params ITaskIdentifier[] ids)
    {
        if (ids == null) throw new ArgumentNullException(nameof(ids));
        if (ids.Length == 0) throw new ArgumentOutOfRangeException(nameof(ids), "must have at least one element in array.");
        EnsureOpen();
        var result = OnGet(ids);
        Log(LogSeverityEnum.Debug, $"Gets {result.Length} items by {ids.Length} identifiers from storage.");
        return result;
    }
    
    public void Set(params ITaskItemData[] items)
    {
        if (items == null) throw new ArgumentNullException(nameof(items));
        if (items.Length == 0) throw new ArgumentOutOfRangeException(nameof(items), "must have at least one element in array.");
        EnsureOpen();
        var storedItems = OnGet(items.Select(x => x as ITaskIdentifier).ToArray());
        var cntUpdated = 0;
        var cntSkipped = 0;
        foreach (var storedItem in storedItems)
        {
            var item = items.First(x => x.Id == storedItem.Id);
            if (item != storedItem)
            {
                OnSetOne(item);
                cntUpdated++;
                Log(LogSeverityEnum.Debug, $"Task item '{item}' has been updated in storage.");
            }
            else
            {
                cntSkipped++;
                Log(LogSeverityEnum.Debug, $"Task item '{item}' skip to update. No relevant changes detected.");
            }
        }

        Log(LogSeverityEnum.Debug, $"Task items has been updated ({cntUpdated} or {items.Length}) in storage (skipped {cntSkipped} of {items.Length}).");
    }

    

    public void Delete(params ITaskIdentifier[] ids)
    {
        if (ids == null) throw new ArgumentNullException(nameof(ids));
        if (ids.Length > 0) throw new ArgumentOutOfRangeException(nameof(ids), "must have at least one element in array.");
        EnsureOpen();
        var items = OnGet(ids);
        foreach (var item in items)
        {
            OnDeleteOne(item);
            Log(LogSeverityEnum.Debug, $"Task item '{item}' has been deleted from storage.");
        }
    }

    public abstract IQueryable<ITaskItemData> Query(IPaging paging, params ProcessStatusEnum[] statuses);

    #region *** Private Operations & Virtuals ***
    protected virtual void Log(LogSeverityEnum type, string message, Exception? e = null)
    {
        _log.Log(type, message, e);
    }

    protected void EnsureOpen()
    {
        if (!IsOpen) Open();
    }

    protected abstract void OnOpen();
    protected abstract void OnClose();
    protected abstract bool OnEnsureStorage();
    protected abstract ITaskIdentifier OnCreate(ITaskItemData item);
    protected abstract ITaskItemData[] OnGet(ITaskIdentifier[] ids);
    protected abstract void OnDeleteOne(ITaskItemData item);

    protected abstract void OnSetOne(ITaskItemData item);
    #endregion
}
