#nullable enable

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using UnityEngine;

namespace HyperstoneSdk
{
    [Serializable]
    public class HyperstoneConfig : ISerializationCallbackReceiver
    {
        [SerializeField] private string _raw;
        private Dictionary<string, JsonToken> _tokens;
        private StringBuilder _sb;

        public IEnumerable<string> Keys
        {
            get => _tokens.Keys;
        }

        internal HyperstoneConfig(string jsonString, Dictionary<string, JsonToken> tokens)
        {
            _raw = jsonString;
            _tokens = tokens;
            _sb = new StringBuilder();
        }

        // For Unity serialization
        private HyperstoneConfig()
        {
            _raw = string.Empty;
            _tokens = new Dictionary<string, JsonToken>();
            _sb = new StringBuilder();
        }

        void ISerializationCallbackReceiver.OnBeforeSerialize()
        {
        }

        void ISerializationCallbackReceiver.OnAfterDeserialize()
        {
            _tokens = Json.ParseObject(_raw) ?? new Dictionary<string, JsonToken>();
            _sb = new StringBuilder();
        }

        public bool TryGetInt(string key, out int value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                if (token.Type == JsonTokenType.TerminalValue)
                {
                    var valueSpan = _raw.AsSpan()[token.Start..token.End];

                    if (int.TryParse(valueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
                    {
                        return true;
                    }
                }
            }

            value = default;
            return false;
        }

        public int GetIntOrDefault(string key, int defaultValue)
        {
            if (TryGetInt(key, out var value))
            {
                return value;
            }

            return defaultValue;
        }

        public bool TryGetLong(string key, out long value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                if (token.Type == JsonTokenType.TerminalValue)
                {
                    var valueSpan = _raw.AsSpan()[token.Start..token.End];

                    if (long.TryParse(valueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
                    {
                        return true;
                    }
                }
            }

            value = default;
            return false;
        }

        public long GetLongOrDefault(string key, long defaultValue)
        {
            if (TryGetLong(key, out var value))
            {
                return value;
            }

            return defaultValue;
        }

        public bool TryGetFloat(string key, out float value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                if (token.Type == JsonTokenType.TerminalValue)
                {
                    var valueSpan = _raw.AsSpan()[token.Start..token.End];

                    if (float.TryParse(valueSpan, NumberStyles.Float, CultureInfo.InvariantCulture, out value))
                    {
                        return true;
                    }
                }
            }

            value = default;
            return false;
        }

        public float GetFloatOrDefault(string key, float defaultValue)
        {
            if (TryGetFloat(key, out var value))
            {
                return value;
            }

            return defaultValue;
        }

        public bool TryGetDouble(string key, out double value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                if (token.Type == JsonTokenType.TerminalValue)
                {
                    var valueSpan = _raw.AsSpan()[token.Start..token.End];

                    if (double.TryParse(valueSpan, NumberStyles.Float, CultureInfo.InvariantCulture, out value))
                    {
                        return true;
                    }
                }
            }

            value = default;
            return false;
        }

        public double GetDoubleOrDefault(string key, double defaultValue)
        {
            if (TryGetDouble(key, out var value))
            {
                return value;
            }

            return defaultValue;
        }

        public bool TryGetString(string key, out string value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                if (token.Type == JsonTokenType.QuotedStringValue)
                {
                    _sb.Clear();
                    var pos = token.Start;
                    if (Json.ParseQuotedString(_raw, ref pos, _sb))
                    {
                        value = _sb.ToString();
                        _sb.Clear();
                        return true;
                    }
                }

                // TODO: support GetString for true/false/json fields?
            }

            value = string.Empty;
            return false;
        }

        public string? GetStringOrDefault(string key, string? defaultValue)
        {
            if (TryGetString(key, out var value))
            {
                return value;
            }

            return defaultValue;
        }

        public bool TryGetBool(string key, out bool value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                if (token.Type != JsonTokenType.TerminalValue)
                {
                    value = default;
                    return false;
                }

                if (string.Compare(_raw, token.Start, "true", 0, 4) == 0)
                {
                    value = true;
                    return true;
                }

                if (string.Compare(_raw, token.Start, "false", 0, 5) == 0)
                {
                    value = false;
                    return true;
                }
            }

            value = default;
            return false;
        }

        public bool GetBoolOrDefault(string key, bool defaultValue)
        {
            if (TryGetBool(key, out var value))
            {
                return value;
            }

            return defaultValue;
        }
        public bool TryGetValueAsJson(string key, out string value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                value = _raw[token.Start..token.End];
                return true;
            }

            value = string.Empty;
            return false;
        }

        public bool TryGetValueAsObject<T>(string key, out T? value)
        {
            if (_tokens.TryGetValue(key, out var token))
            {
                var span = _raw[token.Start..token.End];
                try
                {
                    value = JsonUtility.FromJson<T>(span);
                    return true;
                }
                catch (ArgumentException)
                {
                    value = default;
                    return false;
                }
            }

            value = default;
            return false;
        }

        public T AsObject<T>()
        {
            return JsonUtility.FromJson<T>(_raw);
        }

        public void OverwriteObject(object obj)
        {
            JsonUtility.FromJsonOverwrite(_raw, obj);
        }

        public override string ToString()
        {
            return _raw;
        }

        public static HyperstoneConfig FromJson(string json)
        {
            if (string.IsNullOrEmpty(json))
            {
                return new HyperstoneConfig();
            }

            var tokens = Json.ParseObject(json);
            if (tokens == null)
            {
                Debug.LogWarning("[Hyperstone] Failed to parse json config");
                return new HyperstoneConfig();
            }
            return new HyperstoneConfig(json, tokens);
        }
    }
}
