using Edge.Core.Processor;
using Edge.Core.UniversalApi;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Edge.WebHost.Models;
using Edge.Core.Processor.Dispatcher;
using System.Diagnostics.CodeAnalysis;
using Edge.Core.UniversalApi.Auditing;
using Edge.Core.Processor.Dispatcher.Attributes;
using Edge.Core;

namespace Edge.WebHost.Controllers
{
    public class HomeController : Controller
    {
        public const string UniversalWebApiHandlerRoute = "u";
        //private readonly ILogger<HomeController> _logger;
        private readonly Func<IEnumerable<IProcessor>> processorFactory;

        private UniversalApiHub universalApiHub;
        private UniversalApiInvoker universalApiInvoker;
        private IProcessorMetaConfigAccessor processorMetaConfigAccessor;
        private static JsonSerializerOptions jsonSerializerOptions;
        static HomeController()
        {
            jsonSerializerOptions = new JsonSerializerOptions()
            {
                WriteIndented = true,
                PropertyNameCaseInsensitive = true,
            };
            jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        }

        public HomeController(
            Func<IEnumerable<IProcessor>> processorFactory,
            UniversalApiHub universalApiHub, UniversalApiInvoker universalApiInvoker,
            IProcessorMetaConfigAccessor processorMetaConfigAccessor)
        {
            this.processorFactory = processorFactory;
            this.universalApiHub = universalApiHub;
            this.universalApiInvoker = universalApiInvoker;
            this.processorMetaConfigAccessor = processorMetaConfigAccessor;
        }

        [HttpGet]
        public IActionResult Index()
        {
            var clientAcceptFormatStr = Request.Headers[HeaderNames.Accept].FirstOrDefault();
            var processorCompoundInfos = this.processorFactory().Select(p =>
            {
                var pDesc = p.ProcessorDescriptor();
                return new Tuple<ProcessorDescriptor, MetaPartsDescriptor>(pDesc,
                         pDesc.DeviceHandlerOrApp.GetType().GetCustomAttributes<MetaPartsDescriptor>().FirstOrDefault());
            });
            if (string.IsNullOrEmpty(clientAcceptFormatStr))
                return View("~/Views/Home/Index.cshtml", processorCompoundInfos);
            else if (clientAcceptFormatStr.Contains("text/html"))
                return View("~/Views/Home/Index.cshtml", processorCompoundInfos);
            else if (clientAcceptFormatStr.Contains("application/json"))
                return Ok(processorCompoundInfos);
            else if (clientAcceptFormatStr.Contains("application/xml"))
                return Ok(processorCompoundInfos);
            return View("~/Views/Home/Index.cshtml", processorCompoundInfos);
        }


        [HttpGet]
        public async Task<IActionResult> GetProcessorMetaConfigSchema(string simpleSourceEndpointTypeStr, string configSubSection, string format)
        {
            if (this.processorMetaConfigAccessor == null) return Content("");
            var metaConfigs = await this.processorMetaConfigAccessor.GetMetaConfigAsync(null);
            var configs = metaConfigs.Where(c => c.SourceEndpointFullTypeStr.Contains(simpleSourceEndpointTypeStr));
            if (!(configs?.Any() ?? false)) return Content("");

            //sample: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
            var clientAcceptLanguageStr = Request.Headers[HeaderNames.AcceptLanguage].FirstOrDefault()?.Split(',')[0] ?? "en-us";

            #region FdcServerHostApp

            if (simpleSourceEndpointTypeStr.Contains("FdcServerHostApp", StringComparison.OrdinalIgnoreCase))
            {
                if (configSubSection == "Pump")
                {
                    var pumpJsElements = JsonDocument.Parse(configs.FirstOrDefault().Parts.FirstOrDefault().ParametersJsonArrayStr).RootElement.EnumerateArray().First().GetProperty("PumpConfigs").EnumerateArray().ToArray();
                    var productElements = JsonDocument.Parse(configs.FirstOrDefault().Parts.FirstOrDefault().ParametersJsonArrayStr).RootElement.EnumerateArray().First().GetProperty("ProductConfigs").EnumerateArray().ToArray();
                    var siteLevelNozzleIds = new List<int>();
                    var siteLevelNozzleDisplayStrings = new List<string>();
                    foreach (var pumpJsEle in pumpJsElements)
                    {
                        var pumpId = pumpJsEle.GetProperty("PumpId").GetInt32();
                        var nzJsEles = pumpJsEle.GetProperty("NozzleConfigs").EnumerateArray().ToArray();
                        foreach (var nzJsEle in nzJsEles)
                        {
                            int nozzleLogicalId;
                            if (!nzJsEle.GetProperty("NozzleLogicalId").TryGetInt32(out nozzleLogicalId))
                                nozzleLogicalId = -1;

                            int siteLvlNozzleId;
                            if (!nzJsEle.GetProperty("SiteLevelNozzleId").TryGetInt32(out siteLvlNozzleId))
                                siteLvlNozzleId = -1;

                            int tankNumber;
                            if (!nzJsEle.GetProperty("TankNumber").TryGetInt32(out tankNumber))
                                tankNumber = -1;

                            var productCode = nzJsEle.GetProperty("ProductCode").GetInt32();
                            var productName = productElements.Where(p => p.GetProperty("ProductCode").GetInt32() == productCode).FirstOrDefault().GetProperty("ProductName").GetString();
                            siteLevelNozzleIds.Add(siteLvlNozzleId);
                            if (string.IsNullOrEmpty(format))
                                siteLevelNozzleDisplayStrings.Add(
                                    $"lang-en-us:SiteLevelNozzleId: {siteLvlNozzleId}, pumpId: {pumpId}, nozzleLogicalId: {nozzleLogicalId}, productName: {productName}(code: {productCode }), tankNo.:{ tankNumber}lang-zh-cn:站级枪号: {siteLvlNozzleId}, 加油点号: {pumpId}, 逻辑枪号: {nozzleLogicalId}, 油品名: {productName}(代码: {productCode }), 油罐号.: { tankNumber}"
                                    .LocalizedContent(clientAcceptLanguageStr));
                            else if (format == "simple")
                            {
                                siteLevelNozzleDisplayStrings.Add(
                                    $"lang-en-us:SiteLevelNozzleId: {siteLvlNozzleId}, productName: {productName}(code: {productCode })lang-zh-cn:站级枪号: {siteLvlNozzleId}, 油品名: {productName}(代码: {productCode })"
                                    .LocalizedContent(clientAcceptLanguageStr));
                            }
                        }
                    }

                    var dynamicPumpSchema = "{" +
                        "\"$schema\": \"http://json-schema.org/draft-04/schema\"," +
                        "\"type\": \"integer\"," +
                        "\"enum\": [ " +
                            siteLevelNozzleIds.Select(i => i.ToString()).Aggregate((acc, n) => acc + "," + n) + " ]," +
                        "   \"options\": {" +
                        "       \"enum_titles\": [ " +
                                    siteLevelNozzleDisplayStrings.Select(s => "\"" + s + "\"").Aggregate((acc, n) => acc + "," + n) + " ]" +
                        "   }" +
                        "}";

                    return Content(dynamicPumpSchema);
                }
            }

            #endregion

            return Content("");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="apiType">localmqtt, remotemqtt, webapi</param>
        /// <param name="tags">, split tag string</param>
        /// <param name="format"></param>
        /// <returns></returns>
        [HttpGet]
        public IActionResult ShowMeApi(string apiType, string tags, string format)
        {
            var communicationProvider =
                this.universalApiHub.CommunicationProviders?.Where(p => p.GetType().Name.Contains(apiType, StringComparison.OrdinalIgnoreCase))?.FirstOrDefault();
            var docs = communicationProvider?.GetApiDocuments()?.FilterByTags(tags?.Replace(" ", "")?.Split(",")) ?? Enumerable.Empty<UniversalApiInfoDoc>();
            if (format == "data")
                return Ok(docs);
            else if (format == "pretty")
                return View("~/Views/Home/MqttApiDocPretty.cshtml", docs);
            return Redirect("/swagger");
        }

        public async Task<IActionResult> ProcessorMetaDescriptor(int startIndex = 0, int count = 10, string q = "")
        {
            var clientAcceptFormatStr = Request.Headers[HeaderNames.Accept].FirstOrDefault();
            //sample: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
            var clientAcceptLanguageStr = Request.Headers[HeaderNames.AcceptLanguage].FirstOrDefault()?.Split(',')[0] ?? "en-us";
            IEnumerable<ProcessorMetaDescriptor> pMetaDescriptors = null;
            if (!string.IsNullOrEmpty(q))
            {
                pMetaDescriptors = ObjectInstanceCreator.CurrentDomainProcessorEndPointTypes.ExtractProcessorMetaDescriptor()
                    .Where(d => d.SourceEndpointFullTypeStr.Contains(q.Trim(), StringComparison.OrdinalIgnoreCase));
            }
            else
            {
                pMetaDescriptors = ObjectInstanceCreator.CurrentDomainProcessorEndPointTypes.ExtractProcessorMetaDescriptor().Skip(startIndex).Take(count);
            }

            ReplaceWithLocalizedContent(pMetaDescriptors, clientAcceptLanguageStr);

            if (string.IsNullOrEmpty(clientAcceptFormatStr))
                return View(pMetaDescriptors);
            else if (clientAcceptFormatStr.Contains("text/html"))
                return View(pMetaDescriptors);
            else if (clientAcceptFormatStr.Contains("application/json"))
                return Ok(pMetaDescriptors);
            else if (clientAcceptFormatStr.Contains("application/xml"))
                return Ok(pMetaDescriptors);
            return View(pMetaDescriptors);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="clientAcceptLanguageStr">sample: en-US or zh-CN</param>
        static void ReplaceWithLocalizedContent(IEnumerable<ProcessorMetaDescriptor> source, string clientAcceptLanguageStr)
        {
            foreach (var pDescriptor in source)
            {
                pDescriptor.Description = pDescriptor?.Description?.LocalizedContent(clientAcceptLanguageStr);
                if (pDescriptor.Tags != null)
                {
                    var localizedTags = new List<string>();
                    foreach (var t in pDescriptor.Tags)
                    {
                        var compoundTag = new
                        {
                            TagKey = t.LocalizedContent("en"),
                            LocalizedTagName = t.LocalizedContent(clientAcceptLanguageStr)
                        };
                        localizedTags.Add(System.Text.Json.JsonSerializer.Serialize(compoundTag));
                    }

                    pDescriptor.Tags = localizedTags.ToArray();
                }

                if (string.IsNullOrEmpty(pDescriptor.DisplayName))
                {
                    //SourceEndpointFullTypeStr sample: Edge.Core.Processor.Dispatcher.DefaultDispatcher, Edge.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
                    pDescriptor.DisplayName = pDescriptor.SourceEndpointFullTypeStr.Substring(0, pDescriptor.SourceEndpointFullTypeStr.IndexOf(", Version="));
                }
                else
                    pDescriptor.DisplayName = pDescriptor.DisplayName.LocalizedContent(clientAcceptLanguageStr);

                foreach (var mpGroup in pDescriptor.MetaPartsGroupDescriptors)
                {
                    foreach (var mpDescriptor in mpGroup.MetaPartsDescriptors)
                    {
                        mpDescriptor.Description = mpDescriptor?.Description?.LocalizedContent(clientAcceptLanguageStr);
                        if (string.IsNullOrEmpty(mpDescriptor.DisplayName))
                        {
                            mpDescriptor.DisplayName = mpDescriptor.TypeString;
                        }
                        else
                            mpDescriptor.DisplayName = mpDescriptor.DisplayName.LocalizedContent(clientAcceptLanguageStr);

                        foreach (var pJsons in mpDescriptor.ParametersJsonSchemaStrings)
                        {
                            for (int i = 0; i < pJsons.Count; i++)
                            {
                                pJsons[i] = pJsons[i].LocalizedJsonSchema(clientAcceptLanguageStr);
                            }
                        }
                    }
                }
            }
        }

        [HttpGet]
        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

        //public async Task<IActionResult> Processors()
        //{
        //    return View(this.processors);
        //}

        /// <summary>
        /// all calls to every single processor will be routed here
        /// </summary>
        /// <param name="apiType">service, or property</param>
        /// <param name="targetApiName">service, or property name</param>
        /// <param name="targetProcessorName"></param>
        /// <param name="endpointName"></param>
        /// <param name="inputParameter"></param>
        /// <returns></returns>
        //[ApiExplorerSettings(IgnoreApi = true)]
        [HttpPost]
        [Route(UniversalWebApiHandlerRoute)]
        public async Task<IActionResult> UniversalWebApiHandler(
            [FromQuery(Name = "apitype")] string apiType,
            [FromQuery(Name = "an")] string targetApiName,
            [FromQuery(Name = "pn")] string targetProcessorName,
            [FromQuery(Name = "en")] string endpointName,
            [FromBody] JsonElement inputParameter)
        {
            try
            {
                /* empty post body will treat inputParameters==null, no content json array '[]' will treat inputParameters.Length==0*/
                JsonElement[] inputParameters = null;
                if (inputParameter.ValueKind == JsonValueKind.Array)
                    inputParameters = inputParameter.EnumerateArray().ToArray();
                else if (inputParameter.ValueKind == JsonValueKind.Object)
                    inputParameters = new JsonElement[] { inputParameter };
                else if (inputParameter.ValueKind == JsonValueKind.Undefined)
                { }
                else
                    inputParameters = new JsonElement[] { inputParameter };

                var p = this.processorFactory().FirstOrDefault(p => p.MetaConfigName == targetProcessorName);
                if (p == null) return BadRequest("Could not find processor with name: " + targetProcessorName);
                if (string.IsNullOrEmpty(apiType) || string.IsNullOrEmpty(targetApiName))
                    return BadRequest("Must provide apiType and targetApiName in query");
                var requestContext = new RequestContext(Request);

                if (apiType.ToLower() == "service")
                {
                    var apiCallResult = await this.universalApiInvoker.InvokeUniversalApiServiceAsync(p, targetApiName, inputParameters, requestContext);
                    return Json(apiCallResult, jsonSerializerOptions);
                }
                else if (apiType.ToLower() == "property")
                {
                    object apiCallResult;
                    if (inputParameters == null)
                    {
                        /*call 'get'*/
                        apiCallResult = this.universalApiInvoker.InvokeUniversalApiPropertyGet(p, targetApiName, requestContext);
                    }
                    else
                    {
                        /*call 'set'*/
                        JsonElement? setValue = null;
                        if (inputParameters.Length == 0)
                        {
                        }
                        else if (inputParameters.Length >= 2)
                        {
                            return BadRequest("Unexpected too many parameters passed in. when calling for set value for a Property, Either post a single JsonObject, or a JsonArray with single element of JsonObject");
                        }
                        else
                            setValue = inputParameters[0];
                        apiCallResult = this.universalApiInvoker.InvokeUniversalApiPropertySet(p, targetApiName, setValue, requestContext);
                    }

                    return Json(apiCallResult, jsonSerializerOptions);
                }

                return BadRequest();
            }
            catch (Exception exx)
            {
                return StatusCode(500, exx.ToString());
            }
        }

        public IActionResult Configure()
        {
            //return all descriptor
            IEnumerable<ProcessorMetaDescriptor> descriptors = null;
            descriptors = ObjectInstanceCreator.CurrentDomainProcessorEndPointTypes.ExtractProcessorMetaDescriptor().Skip(0);
            //sample: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
            var clientAcceptLanguageStr = Request.Headers[HeaderNames.AcceptLanguage].FirstOrDefault()?.Split(',')[0] ?? "en-us";
            ReplaceWithLocalizedContent(descriptors, clientAcceptLanguageStr);
            return View(descriptors);
        }

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="appOrHandlerFullName">like Applications.FDC.FdcServerHostApp, or Wayne_Pump_Dart.PumpGroupHandler</param>
        ///// <param name="input"></param>
        ///// <returns></returns>
        //[HttpPost]
        //public async Task<IActionResult> UniversalWebConsoleHandler(
        //   [FromQuery(Name = "epn")] string appOrHandlerFullName,
        //   [FromBody]ApiData input)
        //{
        //    return View("~/Home/WebConsoles/VeederRoot_ATG_Console_Handler");
        //}
    }
}