using System.Globalization;
using System.Runtime.InteropServices.JavaScript;
using System.Text;
using System.Text.Json;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.QConsole;
using Quadarax.Foundation.Core.QConsole.Argument;
using Quadarax.Foundation.Core.QConsole.Attributes;
using Quadarax.Foundation.Core.QConsole.Value;
using Quadarax.Foundation.Core.QMonitor.Interfaces;
using Quadarax.Foundation.Core.Value.Extensions;
using Result = Quadarax.Foundation.Core.Value.Result;

namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
{
    [CommandDefinition]
    internal class MonitorCommand : BaseCommand
    {
        #region *** Constants ***

        private const string CS_CMD_MONITOR_NAME = "monitor";
        private const string CS_CMD_MONITOR_DESC = "Monitor specific single channel";

        protected const string CS_ARG_NAME_IID = "iid";
        private const string CS_ARG_HINT_IID = "<instance_identifier>";
        private const string CS_ARG_DESC_IID = "Specify instance identifier to listen from.";

        protected const string CS_ARG_NAME_CHANNEL = "channel";
        private const string CS_ARG_HINT_CHANNEL = "<chennel>";
        private const string CS_ARG_DESC_CHANNEL = "Specify full channel name to monitor.";

        protected const string CS_ARG_NAME_REFRESH = "refresh";
        private const string CS_ARG_HINT_REFRESH = "<refresh_interval>";
        private const string CS_ARG_DESC_REFRESH = "Specify receiver refresh interval to detect changes. Default is 00:00:05.";

        protected const string CS_ARG_NAME_GENFILE = "genfile";
        private const string CS_ARG_HINT_GENFILE = "<general_file>";
        private const string CS_ARG_DESC_GENFILE = "Use general metadata from json file during initialization (no needs to wait to receive).";

        protected const string CS_ARG_NAME_REWIND = "rewind";
        private const string CS_ARG_HINT_REWIND = "<is_rewind>";
        private const string CS_ARG_DESC_REWIND = "Flag defines if rewind to first available cached data (may live when no cached data), otherwise wait for new data.";

        
        #endregion

        #region *** Properties ***
        public override string Name => CS_CMD_MONITOR_NAME;
        public override string Description => CS_CMD_MONITOR_DESC;
        
        private string InstanceIdentifier { get; set; }
        private string ChannelName { get; set; }
        private TimeSpan RefreshInterval { get; set; }
        private string GeneralFile { get; set; }
        private bool IsRewind { get; set; }
        #endregion

        #region *** Private fields ***

        private long _dumpCnt;
        private MonData? _lastData;
        private IDictionary<string, MonData> _lastDataCollection = new Dictionary<string, MonData>();
        #endregion

        #region *** Constructor ***
        public MonitorCommand(Engine engine) : base(engine)
        {
        }
        #endregion

        #region *** Execute ***
        protected override Result OnExecute()
        {
            WriteInfo($"Refresh interval is set to {RefreshInterval}");
            WriteInfo($"Waiting for channel definition '{InstanceIdentifier}' and channel '{ChannelName}'...");
            using (var hnd = Receiver.CreateHandler(InstanceIdentifier, ChannelName, RefreshInterval,GeneralFile, true))
            {
                hnd.ReceiverHandlerStateChanged += OnReceiverHandlerStateChanged;
                hnd.ReceiverHandlerDataChanged += OnReceiverHandlerDataChanged;
                if (IsRewind)
                {
                    WriteInfo("Rewind to first available cached data.");
                    hnd.Rewind(isBackward:true);
                    hnd.Play(false);
                }
                else
                {
                    hnd.Play(true);
                }
                WriteCaption("To close receiver press any key ...");
                System.Console.ReadKey();
            }
            return new Result();
        }

        #endregion
        #region *** Private Operations ***
        protected override void OnInitialize()
        {
            base.OnInitialize();

            InstanceIdentifier = GetArgumentValueOrDefault<string>(CS_ARG_NAME_IID);
            ChannelName = GetArgumentValueOrDefault<string>(CS_ARG_NAME_CHANNEL);
            RefreshInterval = GetArgumentValueOrDefault<TimeSpan>(CS_ARG_NAME_REFRESH);
            GeneralFile = GetArgumentValueOrDefault<string>(CS_ARG_NAME_GENFILE);
            IsRewind = GetArgumentValueOrDefault<bool>(CS_ARG_NAME_REWIND);
        }


        protected override IEnumerable<AbstractArgument> OnSetupArguments()
        {
            var list = base.OnSetupArguments().ToList();
            list.Add(new NamedArgument(CS_ARG_NAME_IID, CS_ARG_HINT_IID, CS_ARG_DESC_IID, TypeValuesEnum.String,string.Empty,true));
            list.Add(new NamedArgument(CS_ARG_NAME_CHANNEL, CS_ARG_HINT_CHANNEL, CS_ARG_DESC_CHANNEL, TypeValuesEnum.String,string.Empty,true));
            list.Add(new NamedArgument(CS_ARG_NAME_REFRESH, CS_ARG_HINT_REFRESH, CS_ARG_DESC_REFRESH, TypeValuesEnum.TimeSpan,TimeSpan.FromSeconds(5).ToString(),false));
            list.Add(new NamedArgument(CS_ARG_NAME_GENFILE, CS_ARG_HINT_GENFILE, CS_ARG_DESC_GENFILE, TypeValuesEnum.String, string.Empty, false));
            list.Add(new FlagArgument(CS_ARG_NAME_REWIND, CS_ARG_HINT_REWIND, CS_ARG_DESC_REWIND, false));
            return list;
        }

        private void OnReceiverHandlerDataChanged(IReceiverHandler sender, MonData newData)
        {
            RenderData(sender.Descriptor, newData);
        }

        private void OnReceiverHandlerStateChanged(IReceiverHandler sender, ReceiverHandlerStateEnum state)
        {
            WriteInfo($"! Receiver state change to {sender.State.ToString().ToUpper()}");
        }

        private void RenderData(MonItemGeneral descriptor, MonData data)
        {
            var firstLine = $"*** Dump [{_dumpCnt}] {descriptor.ViewType} * {descriptor.Caption} ***";
            WriteInfo(firstLine);
            WriteCaption($"Timestamp: {DateTime.FromFileTimeUtc(data.TimestampFtUtc)} ({data.TimestampFtUtc})");
            WriteCaption($"Name: {data.Name}");
            WriteInfo(new('-', firstLine.Length));
            var diff = _lastData?.Diff(data);
            if (descriptor.ViewType == MonItemGeneral.MonViewType.Item)
            {

                //item view
                foreach (var item in descriptor.Items.OrderBy(x=>x.Order))
                {
                    var sb = new StringBuilder();
                    if (diff != null)
                    {
                        if (diff.ContainsKey(item.Name)) sb.Append("[*] ");
                    }
                    sb.Append(string.IsNullOrEmpty(item.Label) ? item.Name : item.Label).Append(" : ");
                    var properties = ParseProperties(JsonDocument.Parse(data.Data).RootElement);
                    var arg = properties.FirstOrDefault(x => string.Equals(x.Name, item.Name, StringComparison.InvariantCultureIgnoreCase));
                    sb.Append(arg != null ? arg.Value : "N/A");
                    WriteInfo(sb.ToString());
                }
            }

            WriteInfo(new('-', firstLine.Length));
            _lastData = data;
            _dumpCnt++;
        }

        private TypedArgument[] ParseProperties(JsonElement element)
        {
            var result = new List<TypedArgument>();
            foreach (var property in element.EnumerateObject())
            {
                if (property.Value.ValueKind.In(JsonValueKind.False, JsonValueKind.True, JsonValueKind.Number,
                        JsonValueKind.String))
                {
                    TypedArgument? arg = null;
                    if (property.Value.ValueKind.In(JsonValueKind.False, JsonValueKind.True))
                    {
                        arg = new TypedArgument(property.Name, property.Value.GetBoolean());
                    }
                    else if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        result.Add(new TypedArgument(property.Name,null, typeof(string)));
                    }
                    else if (property.Value.ValueKind == JsonValueKind.Number)
                    {
                        result.Add(new TypedArgument(property.Name, property.Value.GetDecimal()));
                    }
                    else if (property.Value.ValueKind == JsonValueKind.String)
                    {
                        result.Add(new TypedArgument(property.Name, property.Value.GetString()));
                    }

                    if (arg != null)
                        result.Add(arg);
                }
                
            }
            return result.ToArray();
        }
        #endregion
        
    }
}
