using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
using Cogwheel;

namespace SessionZero;

public class AppSettings
{
    public string SettingsFilePath { get; set; } = "";
    public Dictionary<string, AppSetting> Settings { get; set; } = new();

    public AppSetting CreateSetting(string settingName, Type settingType)
    {
        return new AppSetting(settingName, settingType);
    }

    public void AddSetting(AppSetting setting)
    {
        if (!Settings.TryAdd(setting.SettingName, setting))
        {
            COGWHEEL.LogError($"Cannot add setting: '{setting.SettingName}'. Setting already exists.");
        }
    }

    public AppSetting? GetSetting(string settingName)
    {
        if (!Settings.TryGetValue(settingName, out var setting))
        {
            COGWHEEL.LogError($"Cannot get setting: '{settingName}'. Setting does not exist.");
            return null;
        }
        
        return setting;
    }

    public void RemoveSetting(string settingName)
    {
        if (!Settings.Remove(settingName))
        {
            COGWHEEL.LogError($"Cannot remove setting: '{settingName}. It may not exist.");
        }
    }

    public bool SetSettingValue<T>(string settingName, T value)
    {
        if (Settings.TryGetValue(settingName, out var setting))
        {
            return setting.SetValue(value);
        }
        
        COGWHEEL.LogError($"Cannot set setting: '{settingName}'. Setting does not exist.");
        return false;
    }

    public bool SetSettingValue(string settingName, string value)
    {
        if (Settings.TryGetValue(settingName, out var setting))
        {
            return setting.SetValue(value);
        }

        COGWHEEL.LogError($"Cannot set setting: '{settingName}'. Setting does not exist.");
        return false;
    }

    public T? GetValue<T>(string settingName)
    {
        if (Settings.TryGetValue(settingName, out var setting))
        {
            return setting.GetValue<T>();
        }
        
        return default;
    }
    
    public string GetValueAsString(string settingName)
    {
        if (!Settings.TryGetValue(settingName, out var setting) || setting == null)
        {
            COGWHEEL.LogError($"Cannot get setting '{settingName}'. Setting does not exist.");
            return string.Empty;
        }

        var type = setting.SettingType;

        try
        {
            var method = typeof(AppSetting).GetMethod(nameof(AppSetting.GetValue))!;
            var generic = method.MakeGenericMethod(type);

            var valueObj = generic.Invoke(setting, null);

            if (valueObj == null)
                return string.Empty;
            
            if (type == typeof(string))
                return (string)valueObj;
            
            if (type.IsPrimitive || type.IsEnum || type == typeof(decimal))
                return Convert.ToString(valueObj) ?? string.Empty;

            return valueObj.ToString() ?? string.Empty;
        }
        catch (Exception ex)
        {
            COGWHEEL.LogError($"Failed to get setting '{settingName}' as string: {ex.Message}");
            return string.Empty;
        }
    }
    
    public void SaveToFile() {}
    public void LoadFromFile() {}
}

public class AppSetting(string settingName, Type settingType)
{
    public string SettingName { get; } = settingName;
    public Type SettingType { get; } = settingType;

    private object? _settingValue;

    public T GetValue<T>()
    {
        if (typeof(T) != SettingType)
        {
            COGWHEEL.LogError($"Cannot get value of setting '{SettingName}': Setting is type '{SettingType.Name}' but GetValue was called with type '{typeof(T).Name}'.'");
        }
        
        return _settingValue is null ? default! : (T)_settingValue;
    }
    

    public bool SetValue<T>(T value)
    {
        if (typeof(T) != SettingType)
        {
            COGWHEEL.LogError($"Cannot set value of setting '{SettingName}': Setting is type '{SettingType.Name}' but SetValue was called with type '{typeof(T).Name}'.'");
            return false;
        }
        
        _settingValue = value!;
        return true;
    }

    public bool SetValue(object? value)
    {
        if (value == null)
        {
            _settingValue = null;
            return true;
        }

        if (SettingType.IsInstanceOfType(value))
        {
            _settingValue = value;
            return true;
        }

        try
        {
            var converted = Convert.ChangeType(value, SettingType);
            _settingValue = converted;
            return true;
        }
        catch
        {
            COGWHEEL.LogError($"Cannot convert value '{value}' to {SettingType.Name} for setting '{SettingName}'.");
            return false;
        }
    }
    
    public bool SetValue(string valueString)
    {
        try
        {
            object? parsedValue = null;

            if (SettingType == typeof(string))
            {
                parsedValue = valueString;
            }
            else if (SettingType == typeof(int))
            {
                if (!int.TryParse(valueString, out var i))
                {
                    COGWHEEL.LogError($"Cannot parse '{valueString}' as int for setting '{SettingName}'.");
                    return false;
                }
                parsedValue = i;
            }
            else if (SettingType == typeof(float))
            {
                if (!float.TryParse(valueString, out var f))
                {
                    COGWHEEL.LogError($"Cannot parse '{valueString}' as float for setting '{SettingName}'.");
                    return false;
                }
                parsedValue = f;
            }
            else if (SettingType == typeof(bool))
            {
                if (!bool.TryParse(valueString, out var b))
                {
                    COGWHEEL.LogError($"Cannot parse '{valueString}' as bool for setting '{SettingName}'.");
                    return false;
                }
                parsedValue = b;
            }
            else
            {
                COGWHEEL.LogError(
                    $"Unsupported setting type '{SettingType.Name}' in SetValue(string) for setting '{SettingName}'.");
                return false;
            }

            _settingValue = parsedValue;
            return true;
        }
        catch (Exception ex)
        {
            COGWHEEL.LogError($"Failed to set setting '{SettingName}' to '{valueString}': {ex.Message}");
            return false;
        }
    }
}

static class AppSettingsCommands
{
    [Command(Name = "setsetting")]
    public static void SetSetting(string settingName, string value)
    {
        if (AppManager.Settings.SetSettingValue(settingName, value))
        {
            COGWHEEL.Log($"Setting '{settingName}' set to '{value}'.");
        }
    }

    [Command(Name = "getsetting")]
    public static void GetSetting(string settingName)
    {
        var value = AppManager.Settings.GetValueAsString(settingName);
        COGWHEEL.Log($"Setting '{settingName}' value is: ({AppManager.Settings.GetSetting(settingName)?.SettingType.Name}){value}.");
    }

    [Command(Name = "listsettings")]
    public static void ListSettings(string settingName = "")
    {
        var outputString = $"-- Settings --\n";
        if (string.IsNullOrEmpty(settingName))
        {
            foreach (var setting in AppManager.Settings.Settings)
            {
                outputString +=
                    $"{setting.Value.SettingName}: {setting.Value.SettingType.Name} = {AppManager.Settings.GetValueAsString(setting.Key)}\n";
            }
            
            AppManager.ConsoleControl.Log(outputString, Colors.CadetBlue);
        }
        else
        {
            COGWHEEL.LogWarning($"Searching for settings is not implemented yet.");
        }
    }
    
    
    // TODO: Go update Cogwheel to accept array arguments somehow
    // [Command(Name = "setting")]
    // public static void SettingCommand(string[] args)
    // {
    //     if (args.Length == 0)
    //     {
    //         COGWHEEL.LogError("No arguments supplied. Usage: 'setting <action>'");
    //     }
    //     
    //     var action = args[0];
    //     List<string> actionArgs = new();
    //     for (int i = 1; i < args.Length; i++)
    //     {
    //         actionArgs.Add(args[i]);
    //     }
    //     
    //     switch (action)
    //     {
    //         case "set":
    //             if (actionArgs.Count == 2)
    //             {
    //                 var settingName = args[0];
    //                 var settingValue = args[1];
    //
    //                 if (AppManager.Settings.SetSettingValue(settingName, settingValue))
    //                 {
    //                     COGWHEEL.Log($"Setting '{settingName}' successfully set to '{settingValue}'.");
    //                 }
    //             }
    //             else
    //             {
    //                 COGWHEEL.LogError($"Invalid number of arguments for '{action}'. Usage: 'setting set <settingName> <settingValue>'");
    //             }
    //             break;
    //         case "get":
    //             if (actionArgs.Count == 1)
    //             {
    //                 var val = AppManager.Settings.GetValueAsString(args[0]);
    //                 COGWHEEL.Log($"Value of Setting '{args[0]}' is '{val}'.");
    //             }
    //             else
    //             {
    //                 COGWHEEL.LogError($"Invalid number of arguments for '{action}'. Usage: 'setting get <settingName>'");
    //             }
    //             break;
    //         case "add":
    //             break;
    //         case "remove":
    //             break;
    //         default:
    //             COGWHEEL.LogError($"Unknown argument '{action}' for 'setting' command. Options are 'set, get, add, remove'.");
    //             break;
    //     }
    // }
}