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

namespace Pilz.Configuration;

public class Settings : ISettings
{
    protected readonly Dictionary<string, JObject> settingsJson = [];
    protected readonly Dictionary<string, IChildSettings> settings = [];
    protected JsonSerializerSettings serializerSettings;

    public virtual IReadOnlyCollection<IChildSettings> Childs => settings.Values;

    public virtual T Get<T>() where T : IChildSettings, ISettingsIdentifier
    {
        if (settings.TryGetValue(T.Identifier, out var valueExisting) && valueExisting is T settingsExisting)
            return settingsExisting;

        if (settingsJson.TryGetValue(T.Identifier, out var valueRaw)
            && valueRaw.ToObject<T>(JsonSerializer.CreateDefault(serializerSettings)) is T settingsDeserialized)
        {
            if (!settings.TryAdd(T.Identifier, settingsDeserialized))
                settings[T.Identifier] = settingsDeserialized;
            return settingsDeserialized;
        }

        if (Activator.CreateInstance<T>() is T settingsNew)
        {
            settingsNew.Reset();
            settings.Add(T.Identifier, settingsNew);
            return settingsNew;
        }

        return default;
    }

    public void Reset()
    {
        foreach (var s in settings.Values)
            s.Reset();
    }

    public virtual string Save(JsonSerializerSettings serializerSettings)
    {
        this.serializerSettings = serializerSettings;
        var serializer = JsonSerializer.CreateDefault(serializerSettings);

        foreach (var kvp in settings)
        {
            var raw = JObject.FromObject(kvp.Value, serializer);
            if (!settingsJson.TryAdd(kvp.Key, raw))
                settingsJson[kvp.Key] = raw;
        }

        var objList = new JObject();
        foreach (var kvp in settingsJson)
            objList.Add(kvp.Key, kvp.Value);

        var objSettings = new JObject
        {
            { "Settings", objList }
        };

        return objSettings.ToString(serializer.Formatting);
    }

    public virtual bool Load(JsonSerializerSettings serializerSettings, string raw)
    {
        this.serializerSettings = serializerSettings;

        var objSettings = JObject.Parse(raw);
        if (!objSettings.TryGetValue("Settings", out var tokenList) || tokenList is not JObject objList)
            return false;

        settingsJson.Clear();
        settings.Clear();

        foreach (var child in objList)
        {
            if (child.Value is not JObject value)
                continue;

            settingsJson.Add(child.Key, value);
        }

        return true;
    }

    protected virtual JToken Serialize(object o, JsonSerializer jsonSerializer)
    {
        using JTokenWriter jTokenWriter = new JTokenWriter();
        jsonSerializer.Serialize(jTokenWriter, o, o.GetType());
        return jTokenWriter.Token;
    }
}