using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.IO;

namespace Txgy.EWS.Client.Common
{
    public static class BusinessConfigManager
    {
        private static readonly object SyncRoot = new object();
        private static BusinessConfig _current;

        public static string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config", "client-settings.json");

        public static BusinessConfig Current
        {
            get
            {
                lock (SyncRoot)
                {
                    return _current ?? (_current = Read());
                }
            }
        }

        public static BusinessConfig Read()
        {
            if (!File.Exists(ConfigPath))
            {
                throw new FileNotFoundException("Business config file was not found.", ConfigPath);
            }

            var json = File.ReadAllText(ConfigPath);
            var config = JsonConvert.DeserializeObject<BusinessConfig>(json);
            NormalizeRuntimeSettings(config);
            Validate(config);
            return config;
        }

        public static BusinessConfig Reload()
        {
            lock (SyncRoot)
            {
                _current = Read();
                FreeSqlTencent.Reset();
                FreeSqlLocalSqLite.Reset();
                return _current;
            }
        }

        public static BusinessConfig Save(BusinessConfig config)
        {
            lock (SyncRoot)
            {
                NormalizeRuntimeSettings(config);
                Validate(config);
                SaveConfig(config);
                _current = Read();
                FreeSqlTencent.Reset();
                FreeSqlLocalSqLite.Reset();
                return _current;
            }
        }

        public static string GetConnectionString(string name)
        {
            var connections = Current.Database.Connections;
            if (connections == null || !connections.TryGetValue(name, out var value) || string.IsNullOrWhiteSpace(value))
            {
                throw new InvalidOperationException($"Business config is missing database connection: {name}");
            }

            return value;
        }

        public static string GetRequiredPath(string value, string name)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new InvalidOperationException($"Business config is missing path: {name}");
            }

            return value;
        }

        public static string GetAppSetting(string key)
        {
            if (!GetLegacyAppSettings().TryGetValue(key, out var token))
            {
                return null;
            }

            if (token == null || token.Type == JTokenType.Null)
            {
                return null;
            }

            return token.Type == JTokenType.String ? token.Value<string>() : token.ToString();
        }

        public static void UpdateAppSetting(string key, string value)
        {
            lock (SyncRoot)
            {
                var root = ReadRoot();
                var appSettings = GetLegacyAppSettings(root);
                if (!appSettings.TryGetValue(key, out var token) || token == null)
                {
                    throw new InvalidOperationException($"Business config does not support writing setting: {key}");
                }

                token.Replace(ConvertValue(token, value));
                SaveRoot(root);
                _current = root.ToObject<BusinessConfig>();
                Validate(_current);
                FreeSqlTencent.Reset();
                FreeSqlLocalSqLite.Reset();
            }
        }

        public static void UpdateRuntimeSettings(int loadDataTimeLenMins, int dataCacheTimeLenMins)
        {
            UpdateDataLookbackHours(loadDataTimeLenMins / 60.0);
        }

        public static void UpdateDataLookbackHours(double dataLookbackHours)
        {
            lock (SyncRoot)
            {
                if (dataLookbackHours <= 0)
                {
                    throw new InvalidOperationException("runtime.dataLookbackHours must be greater than 0.");
                }

                var root = ReadRoot();
                root["runtime"]["dataLookbackHours"] = dataLookbackHours;
                root["runtime"]["dataCacheTimeLenMins"] = (int)Math.Round(dataLookbackHours * 60.0);
                SaveRoot(root);
                _current = root.ToObject<BusinessConfig>();
                NormalizeRuntimeSettings(_current);
                Validate(_current);
            }
        }

        private static JObject ReadRoot()
        {
            if (!File.Exists(ConfigPath))
            {
                throw new FileNotFoundException("Business config file was not found.", ConfigPath);
            }

            return JObject.Parse(File.ReadAllText(ConfigPath));
        }

        private static void SaveConfig(BusinessConfig config)
        {
            var serializer = JsonSerializer.Create(new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                StringEscapeHandling = StringEscapeHandling.EscapeNonAscii,
                ContractResolver = new DefaultContractResolver
                {
                    NamingStrategy = new CamelCaseNamingStrategy
                    {
                        ProcessDictionaryKeys = false
                    }
                }
            });
            string json;
            using (var writer = new StringWriter())
            {
                serializer.Serialize(writer, config);
                json = writer.ToString();
            }
            WriteJson(json);
        }

        private static void SaveRoot(JObject root)
        {
            var serializer = JsonSerializer.Create(new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                StringEscapeHandling = StringEscapeHandling.EscapeNonAscii
            });
            string json;
            using (var writer = new StringWriter())
            {
                serializer.Serialize(writer, root);
                json = writer.ToString();
            }
            WriteJson(json);
        }

        private static void WriteJson(string json)
        {
            var directory = Path.GetDirectoryName(ConfigPath);
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
            var tempPath = ConfigPath + ".tmp";
            File.WriteAllText(tempPath, json, System.Text.Encoding.UTF8);
            if (File.Exists(ConfigPath))
            {
                var backupPath = ConfigPath + ".bak";
                if (File.Exists(backupPath))
                {
                    File.Delete(backupPath);
                }
                File.Replace(tempPath, ConfigPath, backupPath, true);
                if (File.Exists(backupPath))
                {
                    File.Delete(backupPath);
                }
                return;
            }
            File.Move(tempPath, ConfigPath);
        }

        private static Dictionary<string, JToken> GetLegacyAppSettings()
        {
            return GetLegacyAppSettings(ReadRoot());
        }

        private static Dictionary<string, JToken> GetLegacyAppSettings(JObject root)
        {
            return new Dictionary<string, JToken>
            {
                { "api_domain", root["endpoints"]?["apiDomain"] },
                { "AlarmSetting", root["paths"]?["alarmSetting"] },
                { "AlarmLevelConfig", root["paths"]?["alarmLevelConfig"] },
                { "ReportEventLevelSetting", root["paths"]?["reportEventLevelSetting"] },
                { "WorkAreaFilePath", root["paths"]?["workAreaFilePath"] },
                { "StationsCsvFilePath", root["paths"]?["stationsCsvFilePath"] },
                { "CadDwgFilePath", root["paths"]?["cadDwgFilePath"] },
                { "DwgJsonSetting", root["paths"]?["dwgJsonSetting"] },
                { "WavesMseedFilePath", root["paths"]?["wavesMseedFilePath"] },
                { "WavesTxtFilePath", root["paths"]?["wavesTxtFilePath"] },
                { "LocalSqLiteDb", root["paths"]?["localSqLiteDb"] },
                { "DwgSettings", root["paths"]?["dwgSettings"] },
                { "CadSettingsFileName", root["paths"]?["cadSettingsFileName"] },
                { "IsDesign", root["runtime"]?["isDesign"] },
                { "IsRealtime", root["runtime"]?["isRealtime"] },
                { "RefreshInterval", root["runtime"]?["refreshInterval"] },
                { "DataLookbackHours", root["runtime"]?["dataLookbackHours"] },
                { "DataCacheTimeLenMins", root["runtime"]?["dataCacheTimeLenMins"] },
                { "RealtimeResultTable", root["database"]?["tables"]?["realtimeResultTable"] },
                { "RealtimeWaveDataTable", root["database"]?["tables"]?["realtimeWaveDataTable"] },
                { "RealtimeFocalmechanismTable", root["database"]?["tables"]?["realtimeFocalmechanismTable"] },
                { "PostResultTable", root["database"]?["tables"]?["postResultTable"] },
                { "PostWaveDataTable", root["database"]?["tables"]?["postWaveDataTable"] },
                { "PostFocalmechanismTable", root["database"]?["tables"]?["postFocalmechanismTable"] },
                { "CommpanyName", root["branding"]?["commpanyName"] },
                { "SystemNameCn", root["branding"]?["systemNameCn"] },
                { "SystemNameEn", root["branding"]?["systemNameEn"] },
                { "WorkAreaName", root["branding"]?["workAreaName"] },
                { "SystemShortName", root["branding"]?["systemShortName"] },
                { "日报起始时间", root["report"]?["dailyReportStartTime"] },
                { "日报平面图横向偏移", root["report"]?["dailyReportPlanOffsetX"] },
                { "日报平面图纵向偏移", root["report"]?["dailyReportPlanOffsetY"] },
                { "BaseX", root["coordinates"]?["baseX"] },
                { "BaseY", root["coordinates"]?["baseY"] },
                { "BaseZ", root["coordinates"]?["baseZ"] }
            };
        }

        private static JToken ConvertValue(JToken currentValue, string value)
        {
            switch (currentValue.Type)
            {
                case JTokenType.Boolean:
                    return bool.Parse(value);
                case JTokenType.Integer:
                    return int.Parse(value);
                case JTokenType.Float:
                    return double.Parse(value);
                default:
                    return value;
            }
        }

        private static void Validate(BusinessConfig config)
        {
            if (config == null)
            {
                throw new InvalidOperationException("Business config file format is invalid.");
            }

            Require(config.Endpoints?.ApiDomain, "endpoints.apiDomain");
            Require(config.Paths?.AlarmSetting, "paths.alarmSetting");
            Require(config.Paths?.AlarmLevelConfig, "paths.alarmLevelConfig");
            Require(config.Paths?.ReportEventLevelSetting, "paths.reportEventLevelSetting");
            Require(config.Paths?.WorkAreaFilePath, "paths.workAreaFilePath");
            Require(config.Paths?.StationsCsvFilePath, "paths.stationsCsvFilePath");
            Require(config.Paths?.CadDwgFilePath, "paths.cadDwgFilePath");
            Require(config.Paths?.DwgJsonSetting, "paths.dwgJsonSetting");
            Require(config.Paths?.WavesMseedFilePath, "paths.wavesMseedFilePath");
            Require(config.Paths?.WavesTxtFilePath, "paths.wavesTxtFilePath");
            Require(config.Paths?.LocalSqLiteDb, "paths.localSqLiteDb");
            Require(config.Branding?.CommpanyName, "branding.commpanyName");
            Require(config.Branding?.SystemNameCn, "branding.systemNameCn");
            Require(config.Branding?.SystemNameEn, "branding.systemNameEn");
            Require(config.Branding?.WorkAreaName, "branding.workAreaName");
            Require(config.Branding?.SystemShortName, "branding.systemShortName");
            Require(config.Report?.DailyReportStartTime, "report.dailyReportStartTime");
            Require(config.Database?.Tables?.RealtimeResultTable, "database.tables.realtimeResultTable");
            Require(config.Database?.Tables?.RealtimeWaveDataTable, "database.tables.realtimeWaveDataTable");
            Require(config.Database?.Tables?.RealtimeFocalmechanismTable, "database.tables.realtimeFocalmechanismTable");
            Require(config.Database?.Tables?.PostResultTable, "database.tables.postResultTable");
            Require(config.Database?.Tables?.PostWaveDataTable, "database.tables.postWaveDataTable");
            Require(config.Database?.Tables?.PostFocalmechanismTable, "database.tables.postFocalmechanismTable");
            GetConnectionStringFromConfig(config, "TencetnMySQL");
            DateTime.Parse(config.Report.DailyReportStartTime);
        }

        private static void NormalizeRuntimeSettings(BusinessConfig config)
        {
            if (config?.Runtime == null)
            {
                return;
            }

            var runtime = config.Runtime;
            if (runtime.DataLookbackHours <= 0)
            {
                throw new InvalidOperationException("runtime.dataLookbackHours must be greater than 0.");
            }

            runtime.DataCacheTimeLenMins = (int)Math.Round(runtime.DataLookbackHours * 60.0);
        }

        private static void Require(string value, string name)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new InvalidOperationException($"Business config is missing setting: {name}");
            }
        }

        private static void GetConnectionStringFromConfig(BusinessConfig config, string name)
        {
            if (config.Database?.Connections == null ||
                !config.Database.Connections.TryGetValue(name, out var value) ||
                string.IsNullOrWhiteSpace(value))
            {
                throw new InvalidOperationException($"Business config is missing database connection: {name}");
            }
        }
    }
}
