using System;
using System.Threading;
using System.Threading.Tasks;
using Quadarax.Foundation.Core.Logging;
using Quadarax.Foundation.Core.Object;
using Quadarax.Foundation.Core.Thread.Extensions;

namespace Quadarax.Foundation.Core.Thread
{
    public abstract class AbstractWorker : DisposableObject, IWorker
    {

        #region *** Private fields ***
        private Task? _thread;
        private ILog? _log;
        private TimeSpan _delay;
        private CancellationTokenSource? _cancel;
        #endregion

        #region *** Properties ***
        public bool IsRunning { get; private set; }
        public bool IsTransient { get; private set; }
        public string Name { get; }
        public int CurrentThreadId => System.Threading.Thread.CurrentThread.ManagedThreadId;
        public TimeSpan Timeout { get; set; }
        public IWorkerContext? Context { get; private set; }
        #endregion

        #region *** Constructors ***
        protected AbstractWorker() : this($"Worker#{Guid.NewGuid():N}", null)
        {

        }
        protected AbstractWorker(IWorkerContext context) : this($"Worker#{Guid.NewGuid():N}", context)
        {

        }
        protected AbstractWorker(string workerName) : this(workerName, null)
        {

        }
        protected AbstractWorker(string workerName, IWorkerContext? context, ILogger? logger = null)
        {
            if (string.IsNullOrEmpty(workerName))
                throw new ArgumentNullException(nameof(workerName));
            Name = workerName;
            Context = context ?? new BasicWorkerContext();
            _log = logger?.GetLogger(GetType());
            Timeout = TimeSpan.Zero;
            _thread = null;
            _cancel = null;
        }
        #endregion

        #region *** Public Operations ***

        public void Start(bool silentlyIfNotValidState = false)
        {
            Start(TimeSpan.Zero, silentlyIfNotValidState);
        }
        public void Start(TimeSpan runDelay, bool silentlyIfNotValidState = false)
        {
            if (IsTransient || IsRunning)
                if (!silentlyIfNotValidState)
                    throw new InvalidOperationException(
                        $"Worker '{Name}' is in transient or running state. Cannot start.");
                else
                    return;

            OnBeforeStart();

            _cancel = new CancellationTokenSource();
            if (Context != null)
            {
                Context.Cancel = _cancel.Token;
                _delay = runDelay;
                _thread = Task.Factory.StartNew(() => DoInternal(this), Context.Cancel);
            }
            IsRunning = true;
            
        }


        public void Stop(bool silentlyIfNotValidState = false)
        {
            if (IsTransient || !IsRunning)
                if (!silentlyIfNotValidState)
                    throw new InvalidOperationException(
                        $"Worker '{Name}' is in transient or running state. Cannot stop.");
                else
                    return;
            IsTransient = true;
            Abort();
            OnAfterStop();
        }
        #endregion

        #region *** Virtuals and protected ***
        protected virtual void OnBeforeStart()
        {
        }
        protected virtual void OnAfterStop()
        {
        }
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        protected virtual async Task Do(IWorkerContext? context)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            throw new NotImplementedException("AbstractWorker.Do() must be overriden.");
        }

        protected void Log(LogSeverityEnum severity, string message)
        {
            _log?.Log(severity, message);
        }

        protected void Log(LogSeverityEnum severity, string message, Exception exception)
        {
            _log?.Log(severity, message, exception);
        }

        protected override void OnDisposing()
        {
            Abort();
            Context = null;
            _log = null;
        }

        #endregion

        #region *** Private Operations ***
        private async void DoInternal(object context)
        {
            var wk = (AbstractWorker)context;
            if (wk._delay != TimeSpan.Zero)
                wk._thread?.Wait(wk._delay);
            wk.IsTransient = false;
            try
            {
                if (Timeout == TimeSpan.Zero)
                    await wk.Do(wk.Context);
                else
                    await wk.Do(wk.Context).TimeoutAfter(Timeout);
            }
            catch (Exception e)
            {
                wk.Log(LogSeverityEnum.Error, $"Worker '{Name}' failed.", e);
                throw;
            }
            finally
            {
                wk.IsRunning = false;
            }
        }

        private void Abort()
        {
            if (_thread == null)
                return;
            _cancel?.Cancel();
            _cancel?.Dispose();
            _cancel = null;
            _thread = null;
            IsRunning = false;
            IsTransient = false;
            
        }
        #endregion
    }
}
