using EasyDevCore.Common;
using EasyDevCore.Configuration.Configuration;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace EasyDevCore.Configuration
{
    /// <summary>
    /// 
    /// </summary>
    public static class ConfigurationHelper
    {
        /// <summary>
        /// Gets the configuation root.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <returns></returns>
        public static IConfigurationRoot GetConfiguationRoot(this IConfiguration section)
        {
            if (section is IConfigurationRoot)
            {
                return (IConfigurationRoot)section;
            }
            else
            {
                var fieldInfo = section.GetType().GetField("_root", BindingFlags.NonPublic | BindingFlags.Instance);
                var root = fieldInfo.GetValue(section);
                return (IConfigurationRoot)root;
            }
        }

        /// <summary>
        /// Gets the application setting constants.
        /// </summary>
        /// <param name="configurationRoot">The configuration root.</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetAppSettingConstants(this IConfigurationRoot configurationRoot)
        {
            var appContantsSection = configurationRoot.GetSection("AppSetting:Constants");
            var envContantsSection = appContantsSection.GetSection("HOST-ENVIROMENT-VARIABLE");
            Dictionary<string, string> result = new();
            foreach (var kp in appContantsSection.GetChildren().Where(kp => !kp.Key.Equals("HOST-ENVIROMENT-VARIABLE", StringComparison.InvariantCultureIgnoreCase)))
            {
                result[kp.Key] = kp.Value;
            }
            foreach (var kp in envContantsSection.GetChildren())
            {
                bool isPrefer = kp.Key.StartsWith("!");
                string key = isPrefer ? kp.Key.Substring(1) : kp.Key;
                string envValue = null;
                if (!result.ContainsKey(key) || isPrefer)
                {
                    envValue = Environment.GetEnvironmentVariable(kp.Value);
                }
                if (envValue != null)
                {
                    result[key] = envValue;
                }
            }
            return result;
        }

        /// <summary>
        /// Maps the application setting contants.
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <returns></returns>
        public static IConfigurationBuilder MapAppSettingContants(this IConfigurationBuilder builder)
        {
            var root = (builder as IConfigurationRoot);
            var appContantsSection = root.GetSection("AppSettings:Constants");
            Dictionary<string, string> constValues = new();
            foreach (var kp in appContantsSection.GetChildren().Where(x => !string.IsNullOrWhiteSpace(x.Value)))
            {
                constValues[kp.Key] = kp.Value;
            }
            List<string> placeHolders = new();
            foreach (var kp in root.GetAllChildren().Where(x => !string.IsNullOrWhiteSpace(x.Value) && x.Value.Contains("{{") && x.Value.Contains("}}")))
            {
                placeHolders.Clear();
                kp.Value.RegexGroupMatch(@"\{\{(?<V>[^{}].*?)\}\}", (g) =>
                {
                    if(constValues.ContainsKey(g.Value))
                    {
                        placeHolders.Add(g.Value);
                    }
                });
                foreach(var p in placeHolders)
                {
                    kp.Value = kp.Value.Replace("{{" + p + "}}", constValues[p]);
                } 
            }
            return builder;
        }

        /// <summary>
        /// Gets the application setting constants.
        /// </summary>
        /// <param name="configurationRoot">The configuration root.</param>
        /// <returns></returns>
        public static Dictionary<string, string> GetAppSettingEnvConstants(this IConfigurationRoot configurationRoot)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (var kp in configurationRoot.GetSection("AppSetting:Constants:HOST-ENVIROMENT-VARIABLE").GetChildren())
            {
                result.Add(kp.Key, kp.Value);
            }
            return result;
        }

        /// <summary>
        /// Gets the configuration section.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        /// <param name="sectionName">Name of the section.</param>
        /// <returns></returns>
        public static IConfigurationSection GetConfigMapEnvVariableSection(this IConfiguration configuration, string sectionName) => new EasyConfigurationSection(configuration.GetSection(sectionName));

        /// <summary>
        /// Binds the configuration value.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <param name="section">The section.</param>
        /// <param name="sectionName">Name of the section.</param>
        /// <returns></returns>
        public static TModel BindConfigEnvVariable<TModel>(this IConfiguration section, string sectionName) where TModel : class
        {
            return section.GetConfigMapEnvVariableSection(sectionName).Bind<TModel>();
        }

        private static string GetEnvironmentVariable(string key)
        {
            return Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.Process)
                    ?? Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.User)
                    ?? Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.Machine);
        }

        /// <summary>
        /// Translates the configuration value with enviroment macro.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="value">The value.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T ResolveConfigVariable<T>(this IConfiguration section, string value, T defaultValue = default(T))
        {
            var root = section.GetConfiguationRoot();
            var appContantsSection = root.GetSection("AppSettings:Constants");
            var envContantsSection = appContantsSection.GetSection("HOST-ENVIROMENT-VARIABLE");
            Dictionary<string, string> appContants = new();
            value.RegexGroupMatch(@"\{\{(.*?)\}\}", (m) =>
            {
                try
                {
                    string appValue = appContantsSection.GetValue<string>(m.Value);
                    string envValueKey = envContantsSection.GetValue<string>(m.Value);
                    string preferEnvKey = envContantsSection.GetValue<string>("!" + m.Value);
                    string replaceValue = (preferEnvKey == null ? null : GetEnvironmentVariable(preferEnvKey)) ?? appValue ?? (envValueKey == null ? null : GetEnvironmentVariable(envValueKey));
                    if (replaceValue != null)
                    {
                        appContants[m.Value] = replaceValue;
                    }
                }
                catch { }
            });

            foreach (var kp in appContants)
            {
                value = value.Replace(kp.Key, kp.Value);
            }
            if (defaultValue == null)
            {
                return value.ConvertTo<T>();
            }
            else
            {
                return value.ConvertTo<T>(defaultValue);
            }
        }

        /// <summary>
        /// Gets configuration value and auto substitute with constants.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetConfigMapVariable<T>(this IConfiguration section, string key, T defaultValue = default(T))
        {
            return ResolveConfigVariable<T>(section, section.GetSection(key).Value, defaultValue);
        }

        /// <summary>
        /// Binds the specified section.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="section">The section.</param>
        /// <returns></returns>
        /// <exception cref="ConfigurationErrorsException">
        /// </exception>
        public static T Bind<T>(this IConfigurationSection section)
        {
            try
            {
                if (section.Value != null)
                    return section.Value.ConvertTo<T>();

                if (typeof(T).HasInterface<IEnumerable>()
                    && !typeof(T).HasInterface<IDictionary>())
                {
                    var values = section.GetChildren().Map(GetValue);
                    return values.ConvertTo<T>();
                }
            }
            catch (Exception ex)
            {
                var message = $"The {section.Key} setting had an invalid format. " +
                              $"The value \"{section.Value}\" could not be cast to type {typeof(T).FullName}";
                throw new ConfigurationErrorsException(message, ex);
            }

            try
            {
                var to = typeof(T).CreateInstance();
                section.Bind(to);
                return (T)to;
            }
            catch (InvalidOperationException ex)
            {
                throw new ConfigurationErrorsException(ex.Message, ex);
            }
        }

        /// <summary>
        /// Binds the specified section name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="config">The configuration.</param>
        /// <param name="sectionName">Name of the section.</param>
        /// <returns></returns>
        public static T Bind<T>(this IConfiguration config, string sectionName = null) => Bind<T>(config.GetSection(sectionName ?? typeof(T).Name));

        /// <summary>
        /// Existses the specified key.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static bool Exists(this IConfigurationSection section, string key) => section.GetChildren().Any(x => x.Key == key) || section.GetSection(key).Exists();

        /// <summary>
        /// Existses the specified key.
        /// </summary>
        /// <param name="config">The configuration.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static bool Exists(this IConfiguration config, string key) => config.GetChildren().Any(x => x.Key == key) || config.GetSection(key).Exists();

        private static void GetAllChildren(List<IConfigurationSection> list, IConfiguration config)
        {
            foreach(var item in config.GetChildren())
            {
                if (item is IConfigurationSection) list.Add(item);
                GetAllChildren(list, item);
            }
        }

        /// <summary>
        /// Gets all children.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        /// <returns></returns>
        public static IEnumerable<IConfigurationSection> GetAllChildren(this IConfiguration configuration)
        {
            List<IConfigurationSection> list = new();
            GetAllChildren(list, configuration);
            return list;
        }

        /// <summary>Gets the value.</summary>
        /// <param name="section">The section.</param>
        /// <returns>
        ///   <br />
        /// </returns>
        static object GetValue(this IConfigurationSection section)
        {
            if (section == null)
                return null;
            if (section.Value != null)
                return section.Value;

            var children = section.GetChildren();
            var first = children.FirstOrDefault();
            if (first == null)
                return null;

            if (first.Key == "0")
            {
                var to = children.Select(GetValue).ToList();
                if (to.Count > 0)
                    return to;
            }
            else
            {
                var to = new Dictionary<string, object>();
                foreach (var child in children)
                {
                    to[child.Key] = GetValue(child);
                }
                if (to.Count > 0)
                    return to;
            }

            return null;
        }
    }
}
