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

namespace InABox.Core
{
    /// <summary>
    /// Class to manage the storing of form data, including the separation of blob and form data.
    /// </summary>
    public class DFSaveStorage
    {
        public Dictionary<string, object?> FormData { get; private set; }

        public Dictionary<string, object?> BlobData { get; private set; }

        public DFSaveStorage()
        {
            FormData = new Dictionary<string, object?>();
            BlobData = new Dictionary<string, object?>();
        }
        
        public int Count() => FormData.Count();
        

        public void AddValue(string key, object? value)
        {
            FormData.Add(key, value);
        }

        public void AddBlobValue(string key, object? value)
        {
            if(value != null)
            {
                var id = Guid.NewGuid().ToString();
                BlobData.Add(id, value);
                FormData.Add(key, id);
            }
        }

        public object? GetValue(string key) => FormData.GetValueOrDefault(key);

        public DFSaveStorageEntry GetEntry(string key)
        {
            return new DFSaveStorageEntry(this, key);
        }

        public DFLoadStorage ToLoadStorage()
        {
            return new DFLoadStorage(FormData, BlobData);
        }
    }

    public class DFSaveStorageEntry
    {
        private readonly DFSaveStorage storage;

        private readonly string key;

        public DFSaveStorageEntry(DFSaveStorage storage, string key)
        {
            this.storage = storage;
            this.key = key;
        }

        public object? GetValue() => storage.GetValue(key);
        public object? GetValue(string subKey) => storage.GetValue($"{key}.{subKey}");

        public void SetValue(object? value)
        {
            storage.AddValue(key, value);
        }

        public void AddValue(string subKey, object? value)
        {
            storage.AddValue($"{key}.{subKey}", value);
        }

        public void SetBlobValue(object? value)
        {
            storage.AddBlobValue(key, value);
        }

        public void AddBlobValue(string subKey, object? value)
        {
            storage.AddBlobValue($"{key}.{subKey}", value);
        }
    }

    public class DFLoadStorage
    {
        public Dictionary<string, object?> FormData { get; private set; } = new Dictionary<string, object?>();

        public DFLoadStorage() : this(new Dictionary<string, object?>(), null)
        {
            
        }
        
        public DFLoadStorage(Dictionary<string, object?> formData, Dictionary<string, object?>? blobData)
        {
            Load(formData, blobData);
        }

        public void Load(Dictionary<string, object?> formData, Dictionary<string, object?>? blobData)
        {
            FormData = formData;

            if (blobData != null)
            {
                var updates = new List<Tuple<string, object?>>();
                foreach (var (key, value) in FormData)
                {
                    if ((value is string str && blobData.TryGetValue(str, out var blob))
                        || (value is Guid guid && blobData.TryGetValue(guid.ToString(), out blob)))
                    {
                        updates.Add(new Tuple<string, object?>(key, blob));
                    }
                }

                foreach (var (key, value) in updates)
                {
                    FormData[key] = value;
                }
            }
        }
        
        public void Clear() => FormData.Clear();

        public int Count() => FormData.Count();

        /// <summary>
        /// Get a value from the storage, returning <see langword="null"/> if the key does not exist.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object? GetValue(string key)
        {
            return FormData.GetValueOrDefault(key);
        }

        [return: MaybeNull]
        public T GetValue<T>(string key)
        {
            if(FormData.TryGetValue(key, out var value))
            {
                if(value is T t)
                {
                    return t;
                }
                else if(value is string str)
                {
                    if(Guid.TryParse(str, out var id))
                    {
                        if(id is T)
                        {
                            return (T)(object)id;
                        }
                        else
                        {
                            return default;
                        }
                    }
                    try
                    {
                        return Serialization.Deserialize<T>(str, strict: true);
                    }
                    catch
                    {
                        return CoreUtils.ChangeType<T>(value);
                    }
                }
                else if(value is JToken jToken)
                {
                    return Serialization.Deserialize<T>(jToken);
                }
                else
                {
                    return CoreUtils.ChangeType<T>(value);
                }
            }
            return default;
        }

        public bool HasValue(string key)
        {
            return FormData.ContainsKey(key);
        }

        public DFLoadStorageEntry GetEntry(string key)
        {
            return new DFLoadStorageEntry(this, key);
        }

        public IEnumerable<KeyValuePair<string, object?>> Items()
        {
            return FormData;
        }
        
    }

    public class DFLoadStorageEntry
    {
        private readonly DFLoadStorage storage;

        private readonly string key;

        public DFLoadStorageEntry(DFLoadStorage storage, string key)
        {
            this.storage = storage;
            this.key = key;
        }

        /// <summary>
        /// Gets the value from the storage entry, returning <see langword="null"/> if it does not exist.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object? GetValue()
        {
            return storage.GetValue(key);
        }

        [return: MaybeNull]
        public T GetValue<T>()
        {
            return storage.GetValue<T>(key);
        }

        /// <summary>
        /// Gets a sub-value from the storage entry, returning <see langword="null"/> if it does not exist.
        /// </summary>
        /// <param name="subKey"></param>
        /// <returns></returns>
        public object? GetValue(string subKey)
        {
            return storage.GetValue($"{key}.{subKey}");
        }

        [return: MaybeNull]
        public T GetValue<T>(string subKey)
        {
            return storage.GetValue<T>($"{key}.{subKey}");
        }

        public bool HasValue() => storage.HasValue(key);

        public bool HasValue(string subKey) => storage.HasValue($"{key}.{subKey}");

        public IEnumerable<KeyValuePair<string, object?>> SubItems()
        {
            var prefix = $"{key}.";
            foreach(var (key, value) in storage.Items())
            {
                if (key.StartsWith(prefix))
                {
                    yield return new KeyValuePair<string, object?>(key[prefix.Length..], value);
                }
            }
        }
    }
}
