using System.IO.Abstractions;
using System.Net.Sockets;
using Quadarax.Foundation.Core.Logging;
using Quadarax.Foundation.Core.Object;
using Quadarax.Foundation.Core.Thread;

namespace Quadarax.Foundation.Core.QMonitor
{
    public abstract class QMonHostBase<TConfiguration> : DisposableObject
        where TConfiguration : QMonHostConfiguration
    {

        #region *** Fields ***
        private bool _isEnabled;
        private readonly ILog? _log;
        private readonly ILogger? _logger;
        #endregion

        #region *** Properties ***
        protected TConfiguration Configuration { get; }
        protected abstract Uri UriGeneral { get; }
        protected abstract Uri UriData { get; }
        protected abstract TimeSpan IntervalGeneral { get; }
        protected abstract TimeSpan? IntervalData { get; }

        protected LoopWorker? WorkerGeneral { get; private set; }
        protected LoopWorker? WorkerData { get; private set; }

        protected ILogger? Logger => _logger;

        public bool IsEnabled
        {
            get => _isEnabled;
            set => SetEnabled(value);
        }
        protected Json.Binder Binder { get; } = new Json.Binder(new FileSystem(), false, true, 100);

        #endregion

        #region *** Constructors ***

        protected QMonHostBase(TConfiguration configuration, ILogger? externalLogger)
        {
            _logger = externalLogger;
            Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
            _log = externalLogger?.GetLogger(GetType());
            SetEnabled(configuration.Enabled);
        }
        #endregion

        #region *** Private Operations ***
        protected void Open()
        {
            if (!_isEnabled) return;

            if (WorkerGeneral != null) throw new InvalidOperationException("UDP client channel General already open. Close first.");
            if (WorkerData != null) throw new InvalidOperationException("UDP client channel Data already open. Close first.");

            var contextGeneral = new QMonWorkerContext(CreateUdpClient(UriGeneral));
            WorkerGeneral = new LoopWorker("GeneralWorker", contextGeneral,async (_)=>await OnTimerGeneral(contextGeneral), _logger);
            WorkerGeneral.IterationsDelayBetween = IntervalGeneral;
            WorkerGeneral.Start();
            Log(LogSeverityEnum.Debug, $"General worker started with interval '{IntervalGeneral}'");
            if (IntervalData.HasValue)
            {
                var contextData = new QMonWorkerContext(CreateUdpClient(UriData));
                WorkerData = new LoopWorker("GeneralWorker", contextData,async (_)=>await OnTimerData(contextData), _logger);
                WorkerData.IterationsDelayBetween = IntervalData.Value;
                WorkerData.Start();
                Log(LogSeverityEnum.Debug, $"Data worker started with interval '{IntervalData}'");
            }

            OnOpen();
        }
        protected void Close()
        {
            if (!_isEnabled) return;

            WorkerGeneral?.Stop();
            WorkerGeneral?.Dispose();
            WorkerGeneral = null;
            
            WorkerData?.Stop();
            WorkerData?.Dispose();
            WorkerData = null;

            OnClose();
        }

        protected abstract void OnOpen();
        protected abstract void OnClose();

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        protected virtual async Task OnTimerData(QMonWorkerContext context)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            throw new NotImplementedException();
        }

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        protected virtual async Task OnTimerGeneral(QMonWorkerContext context)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            throw new NotImplementedException();
        }
        #endregion

        protected void SetEnabled(bool value)
        {
            if (value == _isEnabled)
                return;
            _isEnabled = value;
            if (_isEnabled)
                Open();
            else
                Close();
        }
        protected override void OnDisposing()
        {
            Close();
        }
        protected void Log(LogSeverityEnum type, string message, Exception? e = null)
        {
            _log?.Log(type, message, e);
        }

        protected abstract UdpClient CreateUdpClient(Uri targetUri);
    }
}
