using NUnit.Framework;
using HyperstoneSdk;
using UnityEngine;

public class MetricsContainerTests
{
    [Test]
    public void Increment_AddsValue()
    {
        var container = new MetricsContainer();
        container.Increment("test_metric", 5);

        var output = container.ToString();
        Assert.That(output, Contains.Substring("test_metric 5"));
    }

    [Test]
    public void Increment_AddsFloatValue_WithInvariantFormatting()
    {
        var container = new MetricsContainer();
        container.Increment("float_metric", 12.345f);

        var output = container.ToString();
        Assert.That(output, Contains.Substring("float_metric 12.345"));
    }

    [Test]
    public void Increment_AccumulatesValue()
    {
        var container = new MetricsContainer();
        container.Increment("test_metric", 5);
        container.Increment("test_metric", 3);

        var output = container.ToString();
        Assert.That(output, Contains.Substring("test_metric 8"));
    }

    [Test]
    public void Increment_WithLabels_FormatesKeyCorrectly()
    {
        var container = new MetricsContainer();
        container.Increment("test_metric", 1, new Label("label1", "value1"));

        var output = container.ToString();
        Assert.That(output, Contains.Substring("test_metric{label1=\"value1\"} 1"));
    }

    [Test]
    public void Increment_WithMultipleLabels_SortsLabelsByKey()
    {
        var container = new MetricsContainer();
        // Insert in reverse alphabetical order
        container.Increment("test_metric", 1,
            new Label("b_label", "2"),
            new Label("a_label", "1"));

        var output = container.ToString();
        // Should be sorted alphabetically by label name
        Assert.That(output, Contains.Substring("test_metric{a_label=\"1\",b_label=\"2\"} 1"));
    }

    [Test]
    public void Increment_SameLabelsDifferentOrder_AccumulatesOnSameMetric()
    {
        var container = new MetricsContainer();
        container.Increment("test_metric", 1,
            new Label("a", "1"),
            new Label("b", "2"));

        container.Increment("test_metric", 2,
            new Label("b", "2"),
            new Label("a", "1"));

        var output = container.ToString();
        Assert.That(output, Contains.Substring("test_metric{a=\"1\",b=\"2\"} 3"));
    }

    [Test]
    public void Increment_EscapesSpecialCharactersInLabels()
    {
        var container = new MetricsContainer();
        container.Increment("test_metric", 1, new Label("msg", "line1\nline2 \"quote\" \\slash"));

        var output = container.ToString();
        // Expected: \n -> \\n, " -> \", \ -> \\
        Assert.That(output, Contains.Substring("msg=\"line1\\nline2 \\\"quote\\\" \\\\slash\""));
    }

    [Test]
    public void ToString_ReturnsPrometheusFormat()
    {
        var container = new MetricsContainer();
        container.Increment("m1", 10);
        container.Increment("m2", 20, new Label("l", "v"));

        var output = container.ToString();
        var lines = output.Split(new[] { '\n' }, System.StringSplitOptions.RemoveEmptyEntries);

        Assert.That(lines, Contains.Item("m1 10"));
        Assert.That(lines, Contains.Item("m2{l=\"v\"} 20"));
    }

    [Test]
    public void Clear_RemovesAllMetrics()
    {
        var container = new MetricsContainer();
        container.Increment("m1", 1);
        container.Clear();

        Assert.That(container.ToString(), Is.Empty);
    }

    [Test]
    public void Parse_ValidString_CreatesCorrectContainer()
    {
        string input = "m1 10.5\nm2{k=\"v\"} 20\nm3{desc=\"a \\\"quoted\\\" value\"} 5";
        var container = MetricsContainer.Parse(input);

        string output = container.ToString();

        Assert.That(output, Contains.Substring("m1 10.5"));
        Assert.That(output, Contains.Substring("m2{k=\"v\"} 20"));
        Assert.That(output, Contains.Substring("m3{desc=\"a \\\"quoted\\\" value\"} 5"));
    }

    [Test]
    public void Parse_EmptyString_ReturnsEmptyContainer()
    {
        var container = MetricsContainer.Parse("");
        Assert.That(container.ToString(), Is.Empty);
    }

    [Test]
    public void Parse_IgnoresCommentsAndEmptyLines()
    {
        string input = "# This is a comment\n\nm1 10\n   \n";
        var container = MetricsContainer.Parse(input);

        Assert.That(container.ToString().Trim(), Is.EqualTo("m1 10"));
    }

    [Test]
    public void Add_MergesMetricsFromAnotherContainer()
    {
        var container1 = new MetricsContainer();
        container1.Increment("m1", 10);
        container1.Increment("m2", 20);

        var container2 = new MetricsContainer();
        container2.Increment("m1", 5);
        container2.Increment("m3", 30);

        container1.Add(container2);

        var output = container1.ToString();
        Assert.That(output, Contains.Substring("m1 15")); // 10 + 5
        Assert.That(output, Contains.Substring("m2 20")); // Original
        Assert.That(output, Contains.Substring("m3 30")); // New
    }

    [Test]
    public void Count_ReturnsNumberOfMetrics()
    {
        var container = new MetricsContainer();
        Assert.That(container.Count, Is.EqualTo(0));

        container.Increment("m1", 1);
        Assert.That(container.Count, Is.EqualTo(1));

        container.Increment("m2", 1);
        Assert.That(container.Count, Is.EqualTo(2));
    }

    [Test]
    public void AddFrom_ParsesAndMergesStringContent()
    {
        var container = new MetricsContainer();
        container.Increment("m1", 10);

        string input = "m1 5\nm2 20";
        container.AddFrom(input);

        var output = container.ToString();
        Assert.That(output, Contains.Substring("m1 15"));
        Assert.That(output, Contains.Substring("m2 20"));
    }
    [Test]
    public void Subtract_RemovesMetrics_WhenValueBecomesZero()
    {
        var container1 = new MetricsContainer();
        container1.Increment("m1", 10);
        container1.Increment("m2", 20);

        var container2 = new MetricsContainer();
        container2.Increment("m1", 10);

        container1.Subtract(container2);

        var output = container1.ToString();
        Assert.That(output, Does.Not.Contain("m1"));
        Assert.That(output, Contains.Substring("m2 20"));
    }

    [Test]
    public void Subtract_DecrementsValue_WhenValueRemainsPositive()
    {
        var container1 = new MetricsContainer();
        container1.Increment("m1", 10);

        var container2 = new MetricsContainer();
        container2.Increment("m1", 4);

        container1.Subtract(container2);

        var output = container1.ToString();
        Assert.That(output, Contains.Substring("m1 6"));
    }

    [Test]
    public void Subtract_IgnoresKeys_NotInOriginalContainer()
    {
        var container1 = new MetricsContainer();
        container1.Increment("m1", 10);

        var container2 = new MetricsContainer();
        container2.Increment("m2", 5);

        container1.Subtract(container2);

        var output = container1.ToString();
        Assert.That(output, Contains.Substring("m1 10"));
        Assert.That(output, Does.Not.Contain("m2"));
    }

    [Test]
    public void Subtract_HandlesFloatingPointPrecision()
    {
        var container1 = new MetricsContainer();
        container1.Increment("m1", 0.3f);

        var container2 = new MetricsContainer();
        container2.Increment("m1", 0.1f);
        container2.Increment("m1", 0.2f);

        container1.Subtract(container2);

        // Should be treated as zero and removed
        var output = container1.ToString();
        Assert.That(output, Is.Empty);
    }

    [Test]
    public void TestSerialization()
    {
        var container = new MetricsContainer();
        container.Increment("m1", 10);
        container.Increment("m2", 20, new Label("l", "v"));

        // Simulate Unity serialization
        string json = JsonUtility.ToJson(container);
        var deserialized = JsonUtility.FromJson<MetricsContainer>(json);

        Assert.That(deserialized.Count, Is.EqualTo(2));
        var output = deserialized.ToString();
        Assert.That(output, Contains.Substring("m1 10"));
        Assert.That(output, Contains.Substring("m2{l=\"v\"} 20"));
    }
}
