using Dalamud.Interface.Internal;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using HSUI.Config.Profiles;
using HSUI.Config.Tree;
using HSUI.Config.Windows;
using HSUI.Helpers;
using HSUI.Interface;
using HSUI.Interface.EnemyList;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.Jobs;
using HSUI.Interface.Party;
using HSUI.Interface.PartyCooldowns;
using HSUI.Interface.StatusEffects;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;

namespace HSUI.Config
{
    public delegate void ConfigurationManagerEventHandler(ConfigurationManager configurationManager);
    public delegate void StrataLevelsEventHandler(ConfigurationManager configurationManager, PluginConfigObject config);
    public delegate void GlobalVisibilityEventHandler(ConfigurationManager configurationManager, VisibilityConfig config);

    public class ConfigurationManager : IDisposable
    {
        public static ConfigurationManager Instance { get; private set; } = null!;

        private BaseNode _configBaseNode;
        private Dictionary<string, BaseNode> _configBaseNodeByProfile;
        public BaseNode ConfigBaseNode
        {
            get => _configBaseNode;
            set
            {
                _configBaseNode = value;
                _mainConfigWindow.node = value;
            }
        }

        private WindowSystem _windowSystem;
        private MainConfigWindow _mainConfigWindow;
        private ChangelogWindow _changelogWindow;
        private GridWindow _gridWindow;
        private ControllerBarKeybindsWindow _controllerBarKeybindsWindow;

        public bool IsConfigWindowOpened => _mainConfigWindow.IsOpen;
        public bool IsChangelogWindowOpened => _changelogWindow.IsOpen;
        public bool ShowingModalWindow = false;

        public GradientDirection GradientDirection
        {
            get
            {
                var config = Instance.GetConfigObject<MiscColorConfig>();
                return config != null ? config.GradientDirection : GradientDirection.None;
            }
        }

        public bool OverrideDalamudStyle
        {
            get
            {
                HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
                return config != null ? config.OverrideDalamudStyle : true;
            }
        }

        public CultureInfo ActiveCultreInfo
        {
            get {
                HUDOptionsConfig config = Instance.GetConfigObject<HUDOptionsConfig>();
                return config == null || config.UseRegionalNumberFormats ? CultureInfo.CurrentCulture : CultureInfo.InvariantCulture;
            }
        }

        public string ConfigDirectory;

        public string CurrentVersion => Plugin.Version;
        public string? PreviousVersion { get; private set; } = null;

        private bool _needsProfileUpdate = false;
        private bool _lockHUD = true;

        public bool LockHUD
        {
            get => _lockHUD;
            set
            {
                if (_lockHUD == value)
                {
                    return;
                }

                _lockHUD = value;
                _mainConfigWindow.IsOpen = value;
                _gridWindow.IsOpen = !value;

                LockEvent?.Invoke(this);

                if (_lockHUD)
                {
                    ConfigBaseNode.NeedsSave = true;
                }
            }
        }

        public bool ShowHUD = true;

        public event ConfigurationManagerEventHandler? ResetEvent;
        public event ConfigurationManagerEventHandler? LockEvent;
        public event ConfigurationManagerEventHandler? ConfigClosedEvent;
        public event StrataLevelsEventHandler? StrataLevelsChangedEvent;
        public event GlobalVisibilityEventHandler? GlobalVisibilityEvent;

        public ConfigurationManager()
        {
            ConfigDirectory = Plugin.PluginInterface.GetPluginConfigDirectory();

            _configBaseNodeByProfile = new Dictionary<string, BaseNode>();
            _configBaseNode = new BaseNode();
            InitializeBaseNode(_configBaseNode);
            _configBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;

            _mainConfigWindow = new MainConfigWindow("HSUI Settings");
            _mainConfigWindow.node = _configBaseNode;
            _mainConfigWindow.CloseAction = () =>
            {
                ConfigClosedEvent?.Invoke(this);

                if (ConfigBaseNode.NeedsSave)
                {
                    SaveConfigurations();
                }

                if (_needsProfileUpdate)
                {
                    UpdateCurrentProfile();
                    _needsProfileUpdate = false;
                }
            };

            string changelog = LoadChangelog();
            _changelogWindow = new ChangelogWindow("HSUI Changelog v" + Plugin.Version, changelog);
            _gridWindow = new GridWindow("Grid ##HSUI");
            _controllerBarKeybindsWindow = new ControllerBarKeybindsWindow();

            _windowSystem = new WindowSystem("HSUI_Windows");
            _windowSystem.AddWindow(_mainConfigWindow);
            _windowSystem.AddWindow(_changelogWindow);
            _windowSystem.AddWindow(_gridWindow);
            _windowSystem.AddWindow(_controllerBarKeybindsWindow);

            CheckVersion();

            Plugin.ClientState.Logout += OnLogout;
            Plugin.ClientState.TerritoryChanged += OnTerritoryChanged;
            Plugin.JobChangedEvent += OnJobChanged;

            _configBaseNode.CreateNodesIfNeeded();
        }

        ~ConfigurationManager()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            if (!disposing)
            {
                return;
            }

            ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
            Plugin.ClientState.Logout -= OnLogout;
            Plugin.ClientState.TerritoryChanged -= OnTerritoryChanged;
            Plugin.JobChangedEvent -= OnJobChanged;

            Instance = null!;
        }

        public static void Initialize() { Instance = new ConfigurationManager(); }

        private void OnConfigObjectReset(BaseNode sender)
        {
            ResetEvent?.Invoke(this);
        }

        private void OnLogout(int type, int code)
        {
            SaveConfigurations();
            ProfilesManager.Instance?.SaveCurrentProfile();
        }

        private void OnTerritoryChanged(ushort territoryId)
        {
            // Persist config on teleport/zone change so changes aren't lost during loading screens
            if (ConfigBaseNode.NeedsSave)
                SaveConfigurations();
        }

        private void OnJobChanged(uint jobId)
        {
            UpdateCurrentProfile();
        }

        private string LoadChangelog()
        {
            string path = Path.Combine(Plugin.AssemblyLocation, "changelog.md");

            try
            {
                string fullChangelog = File.ReadAllText(path);
                string versionChangelog = fullChangelog.Split("#", StringSplitOptions.RemoveEmptyEntries)[0];
                return versionChangelog.Replace(Plugin.Version, "");
            }
            catch (Exception e)
            {
                Plugin.Logger.Error("Error loading changelog: " + e.Message);
            }

            return "";
        }

        private void CheckVersion()
        {
            string path = Path.Combine(ConfigDirectory, "version");

            bool needsBackup = false;
            try
            {
                bool needsWrite = false;

                if (!File.Exists(path))
                {
                    needsWrite = true;
                }
                else
                {
                    PreviousVersion = File.ReadAllText(path);
                    if (PreviousVersion != Plugin.Version)
                    {
                        needsWrite = true;
                        needsBackup = true;
                    }
                }

                _changelogWindow.IsOpen = needsWrite;
                _changelogWindow.AutoClose = true;

                if (needsWrite)
                {
                    File.WriteAllText(path, Plugin.Version);
                }
            }
            catch (Exception e)
            {
                Plugin.Logger.Error("Error checking version: " + e.Message);
            }

            try
            {
                if (needsBackup && PreviousVersion != null)
                {
                    BackupFiles(PreviousVersion);
                }
            }
            catch (Exception e)
            {
                Plugin.Logger.Error("Error making backup: " + e.Message);
            }
        }

        private void BackupFiles(string version)
        {
            string backupsRoot = Path.Combine(ConfigDirectory, "Backups");
            if (!Directory.Exists(backupsRoot))
            {
                Directory.CreateDirectory(backupsRoot);
            }

            string backupPath = Path.Combine(backupsRoot, version);

            foreach (string folderPath in Directory.GetDirectories(ConfigDirectory, "*", SearchOption.AllDirectories))
            {
                if (folderPath.Contains("Backups")) { continue; }

                Directory.CreateDirectory(folderPath.Replace(ConfigDirectory, backupPath));
            }

            foreach (string filePath in Directory.GetFiles(ConfigDirectory, "*.*", SearchOption.AllDirectories))
            {
                File.Copy(filePath, filePath.Replace(ConfigDirectory, backupPath), true);
            }
        }

        #region strata
        public void OnStrataLevelChanged(PluginConfigObject config)
        {
            StrataLevelsChangedEvent?.Invoke(this, config);
        }
        #endregion

        #region visibility
        public void OnGlobalVisibilityChanged(VisibilityConfig config)
        {
            GlobalVisibilityEvent?.Invoke(this, config);
        }
        #endregion

        #region windows
        public void ToggleConfigWindow()
        {
            _mainConfigWindow.Toggle();
        }

        public void OpenConfigWindow()
        {
            _mainConfigWindow.IsOpen = true;
        }

        public void CloseConfigWindow()
        {
            _mainConfigWindow.IsOpen = false;
        }

        public void OpenChangelogWindow()
        {
            _changelogWindow.IsOpen = true;
        }

        public void OpenControllerBarKeybindsWindow()
        {
            _controllerBarKeybindsWindow.IsOpen = true;
        }

        public void Draw()
        {
            _windowSystem.Draw();
        }

        public void AddExtraSectionNode(SectionNode node)
        {
            ConfigBaseNode.AddExtraSectionNode(node);
        }
        #endregion

        #region config getters and setters
        public PluginConfigObject GetConfigObjectForType(Type type)
        {
            MethodInfo? genericMethod = GetType().GetMethod("GetConfigObject");
            MethodInfo? method = genericMethod?.MakeGenericMethod(type);
            return (PluginConfigObject)method?.Invoke(this, null)!;
        }
        public T GetConfigObject<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigObject<T>()!;

        public static PluginConfigObject GetDefaultConfigObjectForType(Type type)
        {
            MethodInfo? method = type.GetMethod("DefaultConfig", BindingFlags.Public | BindingFlags.Static);
            return (PluginConfigObject)method?.Invoke(null, null)!;
        }

        public ConfigPageNode GetConfigPageNode<T>() where T : PluginConfigObject => ConfigBaseNode.GetConfigPageNode<T>()!;

        public void SetConfigObject(PluginConfigObject configObject) => ConfigBaseNode.SetConfigObject(configObject);

        public List<T> GetObjects<T>() => ConfigBaseNode.GetObjects<T>();
        #endregion

        #region load / save / profiles
        public bool IsFreshInstall()
        {
            return Directory.GetDirectories(ConfigDirectory).Length == 0;
        }

        public void LoadOrInitializeFiles()
        {
            try
            {
                if (!IsFreshInstall())
                {
                    LoadConfigurations();
                    // gotta save after initial load store possible version update changes right away
                    SaveConfigurations(true);
                }
            }
            catch (Exception e)
            {
                Plugin.Logger.Error("Error initializing configurations: " + e.Message);

                if (e.StackTrace != null)
                {
                    Plugin.Logger.Error(e.StackTrace);
                }
            }
        }

        public void ForceNeedsSave()
        {
            ConfigBaseNode.NeedsSave = true;
        }

        public void LoadConfigurations()
        {
            PerformV2Migration();
            ConfigBaseNode.Load(ConfigDirectory);
        }

        public void SaveConfigurations(bool forced = false)
        {
            if (!forced && !ConfigBaseNode.NeedsSave)
            {
                return;
            }

            ConfigBaseNode.Save(ConfigDirectory);

            ProfilesManager.Instance?.SaveCurrentProfile();

            ConfigBaseNode.NeedsSave = false;
        }

        public void PerformV2Migration()
        {
            // create necessary folders
            string[] newFolders = new string[] { "Other Elements", "Customization" };
            foreach (string folder in newFolders)
            {
                string path = Path.Combine(ConfigDirectory, folder);
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
            }

            // move files
            Dictionary<string, string> files = new Dictionary<string, string>()
            {
                ["Misc\\Experience Bar.json"] = "Other Elements\\Experience Bar.json",
                ["Misc\\GCD Indicator.json"]  = "Other Elements\\GCD Indicator.json",
                ["Misc\\Limit Break.json"]    = "Other Elements\\Limit Break.json",
                ["Misc\\MP Ticker.json"]      = "Other Elements\\MP Ticker.json",
                ["Misc\\Pull Timer.json"]     = "Other Elements\\Pull Timer.json",
                ["Misc\\Fonts.json"]          = "Customization\\Fonts.json"
            };

            foreach (string key in files.Keys)
            {
                string v1Path = Path.Combine(ConfigDirectory, key);
                string v2Path = Path.Combine(ConfigDirectory, files[key]);

                try
                {
                    if (File.Exists(v1Path) && !File.Exists(v2Path))
                    {
                        File.Move(v1Path, v2Path);
                    }
                }
                catch (Exception e)
                {
                    Plugin.Logger.Error("Error migrating file \"" + v1Path + "\" to v2 config structure: " + e.Message);
                }
            }
        }

        public void UpdateCurrentProfile()
        {
            // dont update the profile on job change when the config window is opened
            if (_mainConfigWindow.IsOpen)
            {
                _needsProfileUpdate = true;
                return;
            }

            ProfilesManager.Instance?.UpdateCurrentProfile();
        }

        public string? ExportCurrentConfigs()
        {
            return ConfigBaseNode.GetBase64String();
        }

        public void OnProfileDeleted(string profileName)
        {
            try
            {
                _configBaseNodeByProfile.Remove(profileName);
            }
            catch { }
        }

        public bool ImportProfile(string oldProfileName, string profileName, string rawString, bool forceLoad = false)
        {
            // cache old profile
            _configBaseNodeByProfile[oldProfileName] = ConfigBaseNode;

            // load profile from cache or from rawString
            BaseNode? loadedNode = null;
            if (forceLoad || !_configBaseNodeByProfile.TryGetValue(profileName, out loadedNode))
            {
                ImportProfileNonCached(rawString, out loadedNode);
            }

            if (loadedNode == null) { return false; }

            if (IsConfigWindowOpened || string.IsNullOrEmpty(loadedNode.SelectedOptionName))
            {
                loadedNode.SelectedOptionName = ConfigBaseNode.SelectedOptionName;
                loadedNode.RefreshSelectedNode();
            }

            ConfigBaseNode.ConfigObjectResetEvent -= OnConfigObjectReset;
            ConfigBaseNode = loadedNode;
            ConfigBaseNode.ConfigObjectResetEvent += OnConfigObjectReset;

            PerformV2Migration();

            ResetEvent?.Invoke(this);

            return true;
        }

        private bool ImportProfileNonCached(string rawString, out BaseNode? node)
        {
            List<string> importStrings = new List<string>(rawString.Trim().Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries));
            ImportData[] imports = importStrings.Select(str => new ImportData(str)).ToArray();

            node = new BaseNode();
            InitializeBaseNode(node);

            Dictionary<Type, PluginConfigObject> oldConfigObjects = new Dictionary<Type, PluginConfigObject>();

            foreach (ImportData importData in imports)
            {
                PluginConfigObject? config = importData.GetObject();
                if (config == null)
                {
                    return false;
                }

                if (!node.SetConfigObject(config))
                {
                    oldConfigObjects.Add(config.GetType(), config);
                }
            }

            if (ProfilesManager.Instance != null)
            {
                node.AddExtraSectionNode(ProfilesManager.Instance.ProfilesNode);
            }

            return true;
        }
        #endregion

        #region initialization
        private static void InitializeBaseNode(BaseNode node)
        {
            // creates node tree in the right order...
            foreach (Type type in ConfigObjectTypes)
            {
                var genericMethod = node.GetType().GetMethod("GetConfigPageNode");
                var method = genericMethod?.MakeGenericMethod(type);
                method?.Invoke(node, null);
            }
        }

        private static Type[] ConfigObjectTypes = new Type[]
        {
            // Unit Frames
            typeof(PlayerUnitFrameConfig),
            typeof(TargetUnitFrameConfig),
            typeof(TargetOfTargetUnitFrameConfig),
            typeof(FocusTargetUnitFrameConfig),

            // Mana Bars
            typeof(PlayerPrimaryResourceConfig),
            typeof(TargetPrimaryResourceConfig),
            typeof(TargetOfTargetPrimaryResourceConfig),
            typeof(FocusTargetPrimaryResourceConfig),

            // Castbars
            typeof(PlayerCastbarConfig),
            typeof(TargetCastbarConfig),
            typeof(TargetOfTargetCastbarConfig),
            typeof(FocusTargetCastbarConfig),

            // Buffs and Debuffs
            typeof(PlayerBuffsListConfig),
            typeof(PlayerDebuffsListConfig),
            typeof(TargetBuffsListConfig),
            typeof(TargetDebuffsListConfig),
            typeof(FocusTargetBuffsListConfig),
            typeof(FocusTargetDebuffsListConfig),
            typeof(CustomEffectsListConfig),

            // Nameplates
            typeof(NameplatesGeneralConfig),
            typeof(PlayerNameplateConfig),
            typeof(EnemyNameplateConfig),
            typeof(PartyMembersNameplateConfig),
            typeof(AllianceMembersNameplateConfig),
            typeof(FriendPlayerNameplateConfig),
            typeof(OtherPlayerNameplateConfig),
            typeof(PetNameplateConfig),
            typeof(NPCNameplateConfig),
            typeof(MinionNPCNameplateConfig),
            typeof(ObjectsNameplateConfig),

            // Party Frames
            typeof(PartyFramesConfig),
            typeof(AllianceFramesHealthBarsConfig),
            typeof(AllianceFramesManaBarConfig),
            typeof(AllianceFramesCastbarConfig),
            typeof(AllianceFramesIconsConfig),
            typeof(AllianceFramesBuffsConfig),
            typeof(AllianceFramesDebuffsConfig),
            typeof(AllianceFramesTrackersConfig),
            typeof(AllianceFramesCooldownListConfig),
            typeof(AllianceFrames1Config),
            typeof(AllianceFrames2Config),
            typeof(PartyFramesHealthBarsConfig),
            typeof(PartyFramesManaBarConfig),
            typeof(PartyFramesCastbarConfig),
            typeof(PartyFramesIconsConfig),
            typeof(PartyFramesBuffsConfig),
            typeof(PartyFramesDebuffsConfig),
            typeof(PartyFramesTrackersConfig),
            typeof(PartyFramesCooldownListConfig),

            // Party Cooldowns
            typeof(PartyCooldownsConfig),
            typeof(PartyCooldownsBarConfig),
            typeof(PartyCooldownsDataConfig),

            // Enemy List
            typeof(EnemyListConfig),
            typeof(EnemyListHealthBarConfig),
            typeof(EnemyListEnmityIconConfig),
            typeof(EnemyListSignIconConfig),
            typeof(EnemyListCastbarConfig),
            typeof(EnemyListBuffsConfig),
            typeof(EnemyListDebuffsConfig),

            // Job Specific Bars
            typeof(PaladinConfig),
            typeof(WarriorConfig),
            typeof(DarkKnightConfig),
            typeof(GunbreakerConfig),

            typeof(WhiteMageConfig),
            typeof(ScholarConfig),
            typeof(AstrologianConfig),
            typeof(SageConfig),

            typeof(MonkConfig),
            typeof(DragoonConfig),
            typeof(NinjaConfig),
            typeof(SamuraiConfig),
            typeof(ReaperConfig),
            typeof(ViperConfig),

            typeof(BardConfig),
            typeof(MachinistConfig),
            typeof(DancerConfig),

            typeof(BlackMageConfig),
            typeof(SummonerConfig),
            typeof(RedMageConfig),
            typeof(BlueMageConfig),
            typeof(PictomancerConfig),

            // Other Elements
            typeof(ExperienceBarConfig),
            typeof(GCDIndicatorConfig),
            typeof(MouseGCDIndicatorConfig),
            typeof(HotbarsConfig),
            typeof(Hotbar1BarConfig),
            typeof(Hotbar2BarConfig),
            typeof(Hotbar3BarConfig),
            typeof(Hotbar4BarConfig),
            typeof(Hotbar5BarConfig),
            typeof(Hotbar6BarConfig),
            typeof(Hotbar7BarConfig),
            typeof(Hotbar8BarConfig),
            typeof(Hotbar9BarConfig),
            typeof(Hotbar10BarConfig),
            typeof(ControllerHotbarsConfig),
            typeof(ControllerBar1Config),
            typeof(ControllerBar2Config),
            typeof(ControllerBar3Config),
            typeof(ControllerBar4Config),
            typeof(ControllerBar5Config),
            typeof(ControllerBar6Config),
            typeof(ControllerBar7Config),
            typeof(ControllerBar8Config),
            typeof(ControllerBarKeybindsConfig),
            typeof(PullTimerConfig),
            typeof(LimitBreakConfig),
            typeof(MPTickerConfig),

            // Colors
            typeof(TanksColorConfig),
            typeof(HealersColorConfig),
            typeof(MeleeColorConfig),
            typeof(RangedColorConfig),
            typeof(CastersColorConfig),
            typeof(RolesColorConfig),
            typeof(MiscColorConfig),

            // Customization
            typeof(FontsConfig),
            typeof(BarTexturesConfig),

            // Visibility
            typeof(GlobalVisibilityConfig),

            // Misc
            typeof(HUDOptionsConfig),
            typeof(WindowClippingConfig),
            typeof(TooltipsConfig),
            typeof(GridConfig),

            // Import
            typeof(ImportConfig)
        };
        #endregion
    }
}