#nullable enable

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

namespace HyperstoneSdk
{
    [Serializable]
    public class MetricsContainer : ISerializationCallbackReceiver
    {
        private Dictionary<string, float> _metrics;
        private StringBuilder _sb;

        [SerializeField] private List<string> _keys = new();
        [SerializeField] private List<float> _values = new();

        public MetricsContainer(Dictionary<string, float> metrics)
        {
            _metrics = metrics;
            _sb = new StringBuilder();
        }

        public MetricsContainer() : this(new Dictionary<string, float>())
        {
        }

        void ISerializationCallbackReceiver.OnBeforeSerialize()
        {
            _keys.Clear();
            _values.Clear();
            foreach (var kvp in _metrics)
            {
                _keys.Add(kvp.Key);
                _values.Add(kvp.Value);
            }
        }

        void ISerializationCallbackReceiver.OnAfterDeserialize()
        {
            _metrics = new Dictionary<string, float>();
            for (int i = 0; i < Math.Min(_keys.Count, _values.Count); i++)
            {
                _metrics[_keys[i]] = _values[i];
            }
            _sb = new StringBuilder();
        }

        public int Count => _metrics.Count;

        public void Increment(string metric, float value)
        {
            if (string.IsNullOrEmpty(metric)) return;

            if (_metrics.TryGetValue(metric, out float current))
            {
                _metrics[metric] = current + value;
            }
            else
            {
                _metrics[metric] = value;
            }
        }

        public void Increment(string metric, float value, params Label[] labels)
        {
            if (string.IsNullOrEmpty(metric)) return;

            if (labels == null || labels.Length == 0)
            {
                Increment(metric, value);
                return;
            }

            // Build the key: metric_name{label1="value1",label2="value2"}
            // Labels should be sorted by name to ensure consistent keys.
            Array.Sort(labels, (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));

            _sb.Clear();
            _sb.Append(metric);
            _sb.Append('{');

            for (int i = 0; i < labels.Length; i++)
            {
                if (i > 0) _sb.Append(',');

                _sb.Append(labels[i].Name);
                _sb.Append("=\"");
                AppendEscapedLabelValue(labels[i].Value);
                _sb.Append('"');
            }

            _sb.Append('}');

            string key = _sb.ToString();

            if (_metrics.TryGetValue(key, out float current))
            {
                _metrics[key] = current + value;
            }
            else
            {
                _metrics[key] = value;
            }
        }

        public void Clear()
        {
            _metrics.Clear();
        }

        public void Add(MetricsContainer other)
        {
            if (other == null) return;
            foreach (var kvp in other._metrics)
            {
                if (_metrics.TryGetValue(kvp.Key, out float current))
                {
                    _metrics[kvp.Key] = current + kvp.Value;
                }
                else
                {
                    _metrics[kvp.Key] = kvp.Value;
                }
            }
        }

        public void Subtract(MetricsContainer other)
        {
            if (other == null) return;            
            List<string> keysToRemove = ListPool<string>.Get();

            foreach (var kvp in other._metrics)
            {
                if (_metrics.TryGetValue(kvp.Key, out float current))
                {
                    float newValue = current - kvp.Value;
                    if (Math.Abs(newValue) < 0.000001f)
                    {
                        keysToRemove.Add(kvp.Key);
                    }
                    else
                    {
                        _metrics[kvp.Key] = newValue;
                    }
                }
            }

            foreach (var key in keysToRemove)
            {
                _metrics.Remove(key);
            }

            ListPool<string>.Release(keysToRemove);
        }

        public override string ToString()
        {
            _sb.Clear();
            foreach (var kvp in _metrics)
            {
                _sb.Append(kvp.Key);
                _sb.Append(' ');
                _sb.Append(kvp.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
                _sb.Append('\n');
            }
            return _sb.ToString();
        }

        public static MetricsContainer Parse(string content)
        {
            var result = new MetricsContainer();
            result.AddFrom(content);
            return result;
        }

        public void AddFrom(string content)
        {
            if (string.IsNullOrEmpty(content)) return;

            var span = content.AsSpan();
            int pos = 0;
            while (pos < span.Length)
            {
                int eol = span.Slice(pos).IndexOfAny('\r', '\n');
                int len = eol == -1 ? span.Length - pos : eol;

                var line = span.Slice(pos, len);

                // Advance pos to next line start
                pos += len;
                if (pos < span.Length && span[pos] == '\r') pos++;
                if (pos < span.Length && span[pos] == '\n') pos++;

                if (line.IsWhiteSpace() || (line.Length > 0 && line[0] == '#')) continue;

                int lastSpaceIndex = line.LastIndexOf(' ');
                if (lastSpaceIndex == -1) continue;

                var keyPart = line.Slice(0, lastSpaceIndex).Trim();
                var valuePart = line.Slice(lastSpaceIndex + 1).Trim();

                if (float.TryParse(valuePart, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float value))
                {
                    // Allocation is unavoidable here for dictionary key unless we use a custom dictionary or alternate lookup (NET 9+)
                    string key = keyPart.ToString();

                    if (_metrics.TryGetValue(key, out float current))
                    {
                        _metrics[key] = current + value;
                    }
                    else
                    {
                        _metrics[key] = value;
                    }
                }
            }
        }

        private void AppendEscapedLabelValue(string val)
        {
            if (string.IsNullOrEmpty(val)) return;

            for (int i = 0; i < val.Length; i++)
            {
                char c = val[i];
                switch (c)
                {
                    case '\\':
                        _sb.Append("\\\\");
                        break;
                    case '"':
                        _sb.Append("\\\"");
                        break;
                    case '\n':
                        _sb.Append("\\n");
                        break;
                    default:
                        _sb.Append(c);
                        break;
                }
            }
        }
    }

    public readonly struct Label
    {
        public readonly string Name;
        public readonly string Value;

        public Label(string name, string value)
        {
            Name = name;
            Value = value;
        }
    }
}
