using AutoMapper;
using Edge.Core.Configuration;
using Edge.Core.Database;
using Edge.Core.Database.Configuration.Models;
using Edge.Core.Parser.BinaryParser;
using Edge.Core.Processor.Communicator;
using Edge.Core.Processor.Dispatcher.Attributes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Edge.Core.Processor.Dispatcher
{
    public class DbJSonLoader : IProcessorLoader, IProcessorMetaConfigAccessor
    {
        protected IServiceProvider services;
        protected static ILogger mainLogger = NullLogger.Instance;
        private Dictionary<IProcessor, ProcessorMetaConfig> processorInstanceAndMetaConfigDic
            = new Dictionary<IProcessor, ProcessorMetaConfig>();
        public DbJSonLoader(IServiceProvider services)
        {
            this.services = services;
            var loggerFactory = services.GetRequiredService<ILoggerFactory>();
            mainLogger = loggerFactory.CreateLogger("Main");
        }

        protected virtual IEnumerable<ProcessorMetaConfig> LoadProcessorMetaConfigs()
        {
            string json = @"[
    {
        ""Id"": 16,
        ""Name"": ""HengshanPaymentTerminal.HengshanPayTerminal.Hengs_938d2e2bd48b45ba_8"",
        ""Description"": """",
        ""Type"": 0,
        ""Parts"": [
            {
                ""Id"": 38,
                ""Type"": 1,
                ""FullTypeString"": ""HengshanPaymentTerminal.HengshanPayTermHandler, HengshanPaymentTerminal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",
                ""ParametersJsonArrayStr"": ""[{\""pumpIds\"":\""1,2,3,4\"",\""pumpSubAddresses\"":[{\""PumpId\"":1,\""SubAddress\"":1},{\""PumpId\"":2,\""SubAddress\"":2},{\""PumpId\"":3,\""SubAddress\"":3},{\""PumpId\"":4,\""SubAddress\"":4}],\""pumpNozzleLogicIds\"":[{\""PumpId\"":1,\""LogicIds\"":\""1\""},{\""PumpId\"":2,\""LogicIds\"":\""1\""},{\""PumpId\"":3,\""LogicIds\"":\""1\""},{\""PumpId\"":4,\""LogicIds\"":\""1\""}],\""pumpSiteNozzleNos\"":[{\""PumpId\"":1,\""SiteNozzleNos\"":\""1\""},{\""PumpId\"":2,\""SiteNozzleNos\"":\""2\""},{\""PumpId\"":3,\""SiteNozzleNos\"":\""3\""},{\""PumpId\"":4,\""SiteNozzleNos\"":\""4\""}],\""nozzleLogicIds\"":[{\""NozzleNo\"":1,\""LogicId\"":1},{\""NozzleNo\"":2,\""LogicId\"":1},{\""NozzleNo\"":3,\""LogicId\"":1},{\""NozzleNo\"":4,\""LogicId\"":1}]}]""
            },
            {
                ""Id"": 40,
                ""Type"": 0,
                ""FullTypeString"": ""Edge.Core.Processor.GenericDeviceProcessor`2, Edge.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",
                ""ParametersJsonArrayStr"": ""[]""
            },
            {
                ""Id"": 41,
                ""Type"": 5,
                ""FullTypeString"": ""Edge.Core.Processor.Communicator.TcpServerCommunicator`1, Edge.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"",
              ""ParametersJsonArrayStr"": ""[23456, 30, \""enabled\"", \""enabled\"", \""2\""]""
            }
        ],
        ""Activated"": true
    }
]";
            IEnumerable<ProcessorMetaConfig> configs = JsonSerializer.Deserialize<IEnumerable<ProcessorMetaConfig>>(json);
            return configs;
            //using (var scope = this.services.CreateScope())
            //{
            //var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
            //var mapper = services.GetRequiredService<IMapper>();
            //var processorMetaConfigs =
            //mapper.Map<IEnumerable<ProcessorMetaConfig>>(
            //    dbContext.ProcessorMetaConfigs.Include(i => i.Parts).OrderByDescending(mc => mc.TimeStamp));

            //}
            // now only for App
            //var systemInternalComponentEndPointTypes = new List<Type>();
            //foreach (var loadedAss in ObjectInstanceCreator.CurrentDomainAssemblies)
            //{
            //    try
            //    {
            //        systemInternalComponentEndPointTypes.AddRange(loadedAss.GetTypes().Where(t =>
            //            // if already loaded via db metaconfig, then don't load again even IsSystemInternalComponent
            //            !processorMetaConfigs.SelectMany(mc => mc.Parts).Any(p => p.FullTypeString != t.AssemblyQualifiedName) &&
            //            !t.IsInterface && !t.IsAbstract &&
            //            (
            //                //t.GetInterfaces().Any(ti =>ti.Name == typeof(IDeviceHandler<,>).Name) 
            //                //||
            //                typeof(IAppProcessor).IsAssignableFrom(t)
            //            ) &&
            //            t.GetCustomAttributes<MetaPartsDescriptor>().Any(att => att.IsSystemInternalComponent)
            //            )
            //            );
            //    }
            //    catch
            //    { }
            //}

            //var processorMetaConfigFromAir = systemInternalComponentEndPointTypes.Select(t => new ProcessorMetaConfig()
            //{
            //    Id = new Random().Next(1000, int.MaxValue),
            //    Activated = true,
            //    Description = "SystemInternalComponent",
            //    Name = "SystemInternalComponent_" + t.Name,
            //    Type = ProcessorTypeEnum.AppProcessor,
            //    Parts = new List<ProcessorMetaPartsConfig>()
            //       {
            //           new ProcessorMetaPartsConfig()
            //           {
            //                Id = new Random().Next(1000, int.MaxValue),
            //                FullTypeString  = t.AssemblyQualifiedName,
            //                Type = ProcessorMetaPartsTypeEnum.App,
            //                ParametersJsonArrayStr = "["+
            //                    t.GetCustomAttributes<MetaPartsDescriptor>().First(att=>att.IsSystemInternalComponent).DefaultCtorParamsJsonStrings.Aggregate((acc,n)=>acc+","+n)+"]",
            //           }
            //       }
            //});
            //return processorMetaConfigs.Concat(processorMetaConfigFromAir);
        }

        public IEnumerable<ProcessorInstanceOperatingResult> CreateAll()
        {
            this.processorInstanceAndMetaConfigDic.Clear();
            mainLogger.LogInformation("Use DbJSonLoader for Processor Meta Config.");
            mainLogger.LogInformation("==============Instantiate Processors (by DbJSonLoader)==============");
            Console.WriteLine();
            Console.WriteLine("==============Instantiate Processors (by DbJSonLoader)==============");

            var processorMetaConfigs = this.LoadProcessorMetaConfigs().Where(c => c.Activated);
            if (processorMetaConfigs == null || !processorMetaConfigs.Any())
            {
                //mainLogger.LogInformation("Found IDeviceHandler or IAppProcessor type: " + t.FullName + " but there's no meta config defined for it, will skip it");
                mainLogger.LogInformation("Found empty MetaConfig in db");
                mainLogger.LogInformation("==============Instantiate Processors End==============");
                Console.WriteLine("==============Instantiate Processors End==============");
                return Enumerable.Empty<ProcessorInstanceOperatingResult>();
            }

            var createResults = new List<ProcessorInstanceOperatingResult>();
            processorMetaConfigs.ToList().ForEach(mc =>
            {
                mainLogger.LogInformation("constructing [id:" + mc.Id + "]: " + mc.Name + ", type: " + mc.Type);// + ", from: " + t.Assembly.FullName + ", assemblyPath: " + t.Assembly.Location);
                Console.WriteLine("[id:" + mc.Id + "]: " + mc.Name);// + " from: " + t.Assembly.GetName().Name);
                try
                {
                    var p = ObjectInstanceCreator.CreateProcessor<IProcessor>(mc, new object[] { this.services });
                    createResults.Add(new ProcessorInstanceOperatingResult()
                    {
                        Succeed = true,
                        ProcessorInstance = p,
                        HostVersion = Assembly.GetAssembly(p.GetType()).GetName().Version.ToString(),
                        CoreVersion = Assembly.GetAssembly(typeof(IProcessor)).GetName().Version.ToString(),
                        MetaConfig = mc,
                    });
                    this.processorInstanceAndMetaConfigDic.Add(p, mc);
                }
                catch (Exception exxxxx)
                {
                    createResults.Add(new ProcessorInstanceOperatingResult()
                    {
                        Succeed = false,
                        MetaConfig = mc,
                        CoreVersion = Assembly.GetAssembly(typeof(IProcessor)).GetName().Version.ToString(),
                        FailedReason = exxxxx.ToString()
                    });
                    mainLogger.LogError(" - Failed for instantiate: " + "[id:" + mc.Id + "]: " + mc.Name + ", type: " + mc.Type
                        //+ ", from: " + t.Assembly.FullName + ", assemblyPath: " + t.Assembly.Location 
                        + Environment.NewLine + "   " + exxxxx.ToString());
                    Console.BackgroundColor = ConsoleColor.Red;
                    Console.ForegroundColor = ConsoleColor.Black;
                    Console.WriteLine(" - Failed for instantiate: " + "[id:" + mc.Id + "]: " + mc.Name + Environment.NewLine + "   " + exxxxx.ToString().Substring(0, 90) + "...");
                    Console.ResetColor();
                }
            });
            mainLogger.LogInformation("==============Instantiate Processors End==============");
            Console.WriteLine("==============Instantiate Processors End==============");
            return createResults;
        }

        public IProcessor Create(string configName)
        {
            if (string.IsNullOrEmpty(configName)) throw new ArgumentException(nameof(configName));
            //ignore the IsActivated
            var processorMetaConfig = this.LoadProcessorMetaConfigs().First(mc => mc.Name == configName);
            var p = ObjectInstanceCreator.CreateProcessor<IProcessor>(processorMetaConfig, new object[] { this.services });
            return p;
        }

        public async Task<ProcessorMetaPartsConfig> GetMetaPartsMetaConfigAsync(
            IProcessor processor, ProcessorMetaPartsTypeEnum metaPartsType)
        {
            if (!this.processorInstanceAndMetaConfigDic.TryGetValue(processor, out ProcessorMetaConfig pMetaConfig))
                return null;
            using (var scope = this.services.CreateScope())
            {
            
                return null;
            }
        }

        public async Task<ProcessorMetaPartsConfig> UpdateMetaPartsMetaConfigAsync(
            IProcessor processor, ProcessorMetaPartsTypeEnum metaPartsType, object[] parameters)
        {
            if (!this.processorInstanceAndMetaConfigDic.TryGetValue(processor, out ProcessorMetaConfig pMetaConfig))
                return null;
            using (var scope = this.services.CreateScope())
            {
                return null;
            }
        }

        public async Task<bool> RemoveMetaPartsMetaConfigAsync(IProcessor processor, int processorMetaPartsConfigId)
        {
            if (!this.processorInstanceAndMetaConfigDic.TryGetValue(processor, out ProcessorMetaConfig pMetaConfig))
                return false;
            using (var scope = this.services.CreateScope())
            {
                return false;
            }
        }

        public async Task<IEnumerable<ProcessorMetaConfig>> GetMetaConfigAsync()
        {
            return await this.GetMetaConfigAsync(null);
        }

        /// <summary>
        /// Get the meta configs by its linked sourceEndpointFullTypeStr with *sql likes* input parameter.
        /// </summary>
        /// <param name="sourceEndpointFullTypeStr"></param>
        /// <returns></returns>
        public async Task<IEnumerable<ProcessorMetaConfig>> GetMetaConfigAsync(string sourceEndpointFullTypeStr)
        {
            return null;
        }

        public async Task<ProcessorMetaConfig> UpsertMetaConfigAsync(ProcessorMetaConfig metaConfig)
        {
            return null;
        }

        public async Task<bool> RemoveMetaConfigAsync(int metaConfigId)
        {
            return false;
        }
    }
}
