using System.Text.Json;
using IronSnappy;

namespace ZB.MOM.NatsNet.Server;

/// <summary>
/// String/JSON helpers for store enums that mirror Go enum methods.
/// </summary>
public static class StoreEnumExtensions
{
    public static string String(this StoreCipher cipher)
        => cipher switch
        {
            StoreCipher.ChaCha => "ChaCha20-Poly1305",
            StoreCipher.Aes => "AES-GCM",
            StoreCipher.NoCipher => "None",
            _ => "Unknown StoreCipher",
        };

    public static string String(this StoreCompression alg)
        => alg switch
        {
            StoreCompression.NoCompression => "None",
            StoreCompression.S2Compression => "S2",
            _ => "Unknown StoreCompression",
        };

    public static byte[] MarshalJSON(this StoreCompression alg)
        => alg switch
        {
            StoreCompression.NoCompression => JsonSerializer.SerializeToUtf8Bytes("none"),
            StoreCompression.S2Compression => JsonSerializer.SerializeToUtf8Bytes("s2"),
            _ => throw new InvalidOperationException("unknown compression algorithm"),
        };

    public static void UnmarshalJSON(this ref StoreCompression alg, ReadOnlySpan<byte> b)
    {
        var parsed = JsonSerializer.Deserialize<string>(b);
        if (parsed == null)
            throw new InvalidDataException("compression value must be a JSON string");

        alg = parsed switch
        {
            "none" => StoreCompression.NoCompression,
            "s2" => StoreCompression.S2Compression,
            _ => throw new InvalidOperationException("unknown compression algorithm"),
        };
    }

    public static void UnmarshalJSON(this ref StoreCompression alg, byte[] b)
    {
        ArgumentNullException.ThrowIfNull(b);
        UnmarshalJSON(ref alg, b.AsSpan());
    }

    public static (byte[]? Buffer, Exception? Error) Compress(this StoreCompression alg, byte[] buf)
    {
        ArgumentNullException.ThrowIfNull(buf);

        const int checksumSize = FileStoreDefaults.RecordHashSize;
        if (buf.Length < checksumSize)
            return (null, new InvalidDataException("uncompressed buffer is too short"));

        return alg switch
        {
            StoreCompression.NoCompression => (buf, null),
            StoreCompression.S2Compression => CompressS2(buf, checksumSize),
            _ => (null, new InvalidOperationException("compression algorithm not known")),
        };
    }

    public static (byte[]? Buffer, Exception? Error) Decompress(this StoreCompression alg, byte[] buf)
    {
        ArgumentNullException.ThrowIfNull(buf);

        const int checksumSize = FileStoreDefaults.RecordHashSize;
        if (buf.Length < checksumSize)
            return (null, new InvalidDataException("compressed buffer is too short"));

        return alg switch
        {
            StoreCompression.NoCompression => (buf, null),
            StoreCompression.S2Compression => DecompressS2(buf, checksumSize),
            _ => (null, new InvalidOperationException("compression algorithm not known")),
        };
    }

    private static (byte[]? Buffer, Exception? Error) CompressS2(byte[] buf, int checksumSize)
    {
        try
        {
            var bodyLength = buf.Length - checksumSize;
            var compressedBody = Snappy.Encode(buf.AsSpan(0, bodyLength));

            var output = new byte[compressedBody.Length + checksumSize];
            Buffer.BlockCopy(compressedBody, 0, output, 0, compressedBody.Length);
            Buffer.BlockCopy(buf, bodyLength, output, compressedBody.Length, checksumSize);
            return (output, null);
        }
        catch (Exception ex)
        {
            return (null, new IOException("error writing to compression writer", ex));
        }
    }

    private static (byte[]? Buffer, Exception? Error) DecompressS2(byte[] buf, int checksumSize)
    {
        try
        {
            var bodyLength = buf.Length - checksumSize;
            var decompressedBody = Snappy.Decode(buf.AsSpan(0, bodyLength));

            var output = new byte[decompressedBody.Length + checksumSize];
            Buffer.BlockCopy(decompressedBody, 0, output, 0, decompressedBody.Length);
            Buffer.BlockCopy(buf, bodyLength, output, decompressedBody.Length, checksumSize);
            return (output, null);
        }
        catch (Exception ex)
        {
            return (null, new IOException("error reading compression reader", ex));
        }
    }
}
