// Go reference: server/events.go:2082-2090 — compressionType / snappyCompression,
// and events.go:578-598 — internalSendLoop optional compression branch.

using System.Text;
using NATS.Server.Events;

namespace NATS.Server.Monitoring.Tests.Events;

/// <summary>
/// Tests for <see cref="EventCompressor"/> — S2/Snappy compression for system event payloads.
/// Go reference: server/events.go — compressed system events via snappyCompression.
/// </summary>
public class EventCompressionTests : IDisposable
{
    public EventCompressionTests()
    {
        // Ensure a clean statistics baseline for every test.
        EventCompressor.ResetStats();
    }

    public void Dispose()
    {
        EventCompressor.ResetStats();
    }

    // ── 1 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void Compress_ValidPayload_ReturnsCompressed()
    {
        // Arrange
        var json = """{"server":{"name":"s1","id":"ABCDEF"},"data":{"conns":42,"bytes":1024}}""";
        var payload = Encoding.UTF8.GetBytes(json);

        // Act
        var compressed = EventCompressor.Compress(payload);

        // Assert
        compressed.ShouldNotBeNull();
        compressed.Length.ShouldBeGreaterThan(0);
        // Snappy output begins with a varint for the original length — not the same raw bytes.
        compressed.ShouldNotBe(payload);
    }

    // ── 2 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void Decompress_RoundTrip_MatchesOriginal()
    {
        // Arrange
        var original = Encoding.UTF8.GetBytes(
            """{"server":{"name":"test","id":"XYZ"},"stats":{"cpu":0.5,"mem":1048576}}""");

        // Act
        var compressed   = EventCompressor.Compress(original);
        var decompressed = EventCompressor.Decompress(compressed);

        // Assert
        decompressed.ShouldBe(original);
    }

    // ── 3 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void ShouldCompress_BelowThreshold_ReturnsFalse()
    {
        // 100 bytes is well below the default 256-byte threshold.
        EventCompressor.ShouldCompress(100).ShouldBeFalse();
    }

    // ── 4 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void ShouldCompress_AboveThreshold_ReturnsTrue()
    {
        // 500 bytes exceeds the default 256-byte threshold.
        EventCompressor.ShouldCompress(500).ShouldBeTrue();
    }

    // ── 5 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void CompressIfBeneficial_SmallPayload_NotCompressed()
    {
        // Arrange — 50 bytes is below the 256-byte threshold.
        var payload = Encoding.UTF8.GetBytes("small");

        // Act
        var (data, compressed) = EventCompressor.CompressIfBeneficial(payload);

        // Assert
        compressed.ShouldBeFalse();
        data.ShouldBe(payload);
    }

    // ── 6 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void CompressIfBeneficial_LargePayload_Compressed()
    {
        // Arrange — build a payload well above the 256-byte threshold.
        var largeJson = """{"server":{"name":"s1"},"data":""" + new string('x', 500) + "}";
        var payload = Encoding.UTF8.GetBytes(largeJson);
        payload.Length.ShouldBeGreaterThan(256);

        // Act
        var (data, isCompressed) = EventCompressor.CompressIfBeneficial(payload);

        // Assert
        isCompressed.ShouldBeTrue();
        // The returned bytes should decompress back to the original.
        var restored = EventCompressor.Decompress(data);
        restored.ShouldBe(payload);
    }

    // ── 7 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void GetCompressionRatio_Calculates()
    {
        // 100 / 200 = 0.5
        var ratio = EventCompressor.GetCompressionRatio(originalSize: 200, compressedSize: 100);

        ratio.ShouldBe(0.5, tolerance: 0.001);
    }

    // ── 8 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void TotalCompressed_IncrementedOnCompress()
    {
        // Arrange — stats were reset in constructor.
        EventCompressor.TotalCompressed.ShouldBe(0L);

        var largePayload = Encoding.UTF8.GetBytes(new string('a', 512));

        // Act — two calls that exceed threshold.
        EventCompressor.CompressIfBeneficial(largePayload);
        EventCompressor.CompressIfBeneficial(largePayload);

        // Assert
        EventCompressor.TotalCompressed.ShouldBe(2L);
    }

    // ── 9 ──────────────────────────────────────────────────────────────────────
    [Fact]
    public void BytesSaved_TracksCorrectly()
    {
        // Arrange
        // Use a highly-compressible payload so savings are guaranteed.
        var payload = Encoding.UTF8.GetBytes(new string('z', 1024));

        // Act
        EventCompressor.CompressIfBeneficial(payload);

        // Assert — compressed version of 1 024 repeated bytes should be much smaller.
        EventCompressor.BytesSaved.ShouldBeGreaterThan(0L);
        // BytesSaved = original - compressed; should be less than original size.
        EventCompressor.BytesSaved.ShouldBeLessThan(payload.Length);
    }

    // ── 10 ─────────────────────────────────────────────────────────────────────
    [Fact]
    public void ResetStats_ClearsAll()
    {
        // Arrange — produce some stats first.
        var largePayload = Encoding.UTF8.GetBytes(new string('b', 512));
        EventCompressor.CompressIfBeneficial(largePayload);
        EventCompressor.TotalCompressed.ShouldBeGreaterThan(0L);

        // Act
        EventCompressor.ResetStats();

        // Assert
        EventCompressor.TotalCompressed.ShouldBe(0L);
        EventCompressor.TotalUncompressed.ShouldBe(0L);
        EventCompressor.BytesSaved.ShouldBe(0L);
    }

    [Fact]
    public void GetAcceptEncoding_ParsesSnappyAndGzip()
    {
        EventCompressor.GetAcceptEncoding("gzip, snappy").ShouldBe(EventCompressionType.Snappy);
        EventCompressor.GetAcceptEncoding("gzip").ShouldBe(EventCompressionType.Gzip);
        EventCompressor.GetAcceptEncoding("br").ShouldBe(EventCompressionType.Unsupported);
        EventCompressor.GetAcceptEncoding(null).ShouldBe(EventCompressionType.None);
    }

    [Fact]
    public void CompressionHeaderConstants_MatchGo()
    {
        EventCompressor.AcceptEncodingHeader.ShouldBe("Accept-Encoding");
        EventCompressor.ContentEncodingHeader.ShouldBe("Content-Encoding");
    }

    [Fact]
    public void CompressAndDecompress_Gzip_RoundTrip_MatchesOriginal()
    {
        var payload = Encoding.UTF8.GetBytes("""{"server":"s1","data":"gzip-payload"}""");

        var compressed = EventCompressor.Compress(payload, EventCompressionType.Gzip);
        var restored = EventCompressor.Decompress(compressed, EventCompressionType.Gzip);

        restored.ShouldBe(payload);
    }
}
