// Reference: golang/nats-server/server/route.go — S2/Snappy compression for route connections
// Go uses s2 (Snappy variant) for route and gateway wire compression.
// IronSnappy provides compatible Snappy block encode/decode.

using IronSnappy;

namespace NATS.Server.Routes;

/// <summary>
/// Compression levels for route wire traffic, matching Go's <c>CompressionMode</c>.
/// </summary>
public enum RouteCompressionLevel
{
    /// <summary>No compression — data passes through unchanged.</summary>
    Off = 0,

    /// <summary>Fastest compression (Snappy/S2 default).</summary>
    Fast = 1,

    /// <summary>Better compression ratio at moderate CPU cost.</summary>
    Better = 2,

    /// <summary>Best compression ratio (highest CPU cost).</summary>
    Best = 3,
}

/// <summary>
/// S2/Snappy compression codec for route and gateway wire traffic.
/// Mirrors Go's route compression (server/route.go) using IronSnappy.
/// </summary>
public static class RouteCompressionCodec
{
    // Snappy block format: the first byte is a varint-encoded length.
    // Snappy stream format starts with 0xff 0x06 0x00 0x00 "sNaPpY" magic.
    // For block format (which IronSnappy uses), compressed output starts with
    // a varint for the uncompressed length, then chunk tags. We detect by
    // attempting a decode-length check: valid Snappy blocks have a leading
    // varint that decodes to a plausible uncompressed size.
    //
    // Snappy stream magic header (10 bytes):
    private static ReadOnlySpan<byte> SnappyStreamMagic => [0xff, 0x06, 0x00, 0x00, 0x73, 0x4e, 0x61, 0x50, 0x70, 0x59];

    /// <summary>
    /// Compresses <paramref name="data"/> using Snappy block format.
    /// If <paramref name="level"/> is <see cref="RouteCompressionLevel.Off"/>,
    /// the original data is returned unchanged (copied).
    /// </summary>
    /// <remarks>
    /// IronSnappy only supports a single compression level (equivalent to Fast/S2).
    /// The <paramref name="level"/> parameter is accepted for API parity with Go
    /// but Fast, Better, and Best all produce the same output.
    /// </remarks>
    public static byte[] Compress(ReadOnlySpan<byte> data, RouteCompressionLevel level = RouteCompressionLevel.Fast)
    {
        if (level == RouteCompressionLevel.Off)
            return data.ToArray();

        if (data.IsEmpty)
            return [];

        return Snappy.Encode(data);
    }

    /// <summary>
    /// Decompresses Snappy/S2-compressed <paramref name="compressed"/> data.
    /// </summary>
    /// <exception cref="InvalidOperationException">If the data is not valid Snappy.</exception>
    public static byte[] Decompress(ReadOnlySpan<byte> compressed)
    {
        if (compressed.IsEmpty)
            return [];

        return Snappy.Decode(compressed);
    }

    /// <summary>
    /// Negotiates the effective compression level between two peers.
    /// Returns the minimum (least aggressive) of the two levels, matching
    /// Go's negotiation behavior where both sides must agree.
    /// If either side is Off, the result is Off.
    /// </summary>
    public static RouteCompressionLevel NegotiateCompression(string localLevel, string remoteLevel)
    {
        var local = ParseLevel(localLevel);
        var remote = ParseLevel(remoteLevel);

        if (local == RouteCompressionLevel.Off || remote == RouteCompressionLevel.Off)
            return RouteCompressionLevel.Off;

        // Return the minimum (least aggressive) level
        return (RouteCompressionLevel)Math.Min((int)local, (int)remote);
    }

    /// <summary>
    /// Detects whether the given data appears to be Snappy-compressed.
    /// Checks for Snappy stream magic header or attempts to validate
    /// as a Snappy block format by checking the leading varint.
    /// </summary>
    public static bool IsCompressed(ReadOnlySpan<byte> data)
    {
        if (data.Length < 2)
            return false;

        // Check for Snappy stream format magic
        if (data.Length >= SnappyStreamMagic.Length && data[..SnappyStreamMagic.Length].SequenceEqual(SnappyStreamMagic))
            return true;

        // For Snappy block format, try to decode and see if it succeeds.
        // A valid Snappy block starts with a varint for the uncompressed length.
        try
        {
            _ = Snappy.Decode(data);
            return true;
        }
        catch
        {
            return false;
        }
    }

    private static RouteCompressionLevel ParseLevel(string level)
    {
        if (string.IsNullOrWhiteSpace(level))
            return RouteCompressionLevel.Off;

        return level.Trim().ToLowerInvariant() switch
        {
            "off" or "disabled" or "none" => RouteCompressionLevel.Off,
            "fast" or "s2_fast" => RouteCompressionLevel.Fast,
            "better" or "s2_better" => RouteCompressionLevel.Better,
            "best" or "s2_best" => RouteCompressionLevel.Best,
            _ => RouteCompressionLevel.Off,
        };
    }
}
