using AyCode.Core.Serializers.Toons;
using System.Buffers;
using System.IO.Compression;
using System.Runtime.CompilerServices;
using System.Text;

namespace AyCode.Core.Compression;

/// <summary>
/// Brotli compression/decompression helper for SignalR message transport.
/// Optimized for zero-allocation scenarios with pooled buffers.
/// </summary>
public static class BrotliHelper
{
    //[ToonDescription("Unique identifier for the person")]
    private const int DefaultBufferSize = 4096;
    private const int MaxStackAllocSize = 1024;

    #region Compression

    /// <summary>
    /// Compresses a string using Brotli compression with pooled buffers.
    /// </summary>
    public static byte[] Compress(string text, CompressionLevel compressionLevel = CompressionLevel.Optimal)
    {
        if (string.IsNullOrEmpty(text))
            return [];

        // Use stack allocation for small strings, pooled buffer for larger
        var maxByteCount = Encoding.UTF8.GetMaxByteCount(text.Length);
        
        if (maxByteCount <= MaxStackAllocSize)
        {
            Span<byte> utf8Bytes = stackalloc byte[maxByteCount];
            var actualLength = Encoding.UTF8.GetBytes(text.AsSpan(), utf8Bytes);
            return CompressSpan(utf8Bytes[..actualLength], compressionLevel);
        }
        
        var rentedBuffer = ArrayPool<byte>.Shared.Rent(maxByteCount);
        try
        {
            var actualLength = Encoding.UTF8.GetBytes(text.AsSpan(), rentedBuffer);
            return CompressSpan(rentedBuffer.AsSpan(0, actualLength), compressionLevel);
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(rentedBuffer);
        }
    }

    /// <summary>
    /// Compresses a byte array using Brotli compression.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte[] Compress(byte[] data, CompressionLevel compressionLevel = CompressionLevel.Optimal)
        => data == null || data.Length == 0 ? [] : CompressSpan(data.AsSpan(), compressionLevel);

    /// <summary>
    /// Compresses a ReadOnlySpan using Brotli compression with pooled output buffer.
    /// </summary>
    public static byte[] CompressSpan(ReadOnlySpan<byte> data, CompressionLevel compressionLevel = CompressionLevel.Optimal)
    {
        if (data.IsEmpty)
            return [];

        using var outputStream = new MemoryStream();
        using (var brotliStream = new BrotliStream(outputStream, compressionLevel, leaveOpen: true))
        {
            brotliStream.Write(data);
        }
        return outputStream.ToArray();
    }

    #endregion

    #region Decompression

    /// <summary>
    /// Decompresses Brotli-compressed data to a string.
    /// Consider using Decompress + direct UTF-8 deserialization for better performance.
    /// </summary>
    public static string DecompressToString(byte[] compressedData)
    {
        if (compressedData == null || compressedData.Length == 0)
            return string.Empty;

        var decompressedBytes = Decompress(compressedData);
        return Encoding.UTF8.GetString(decompressedBytes);
    }

    /// <summary>
    /// Decompresses Brotli-compressed data to a byte array.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte[] Decompress(byte[] compressedData)
    {
        if (compressedData == null || compressedData.Length == 0)
            return [];

        return DecompressCore(compressedData);
    }

    /// <summary>
    /// Decompresses Brotli-compressed data from a ReadOnlySpan.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte[] DecompressSpan(ReadOnlySpan<byte> compressedData)
    {
        if (compressedData.IsEmpty)
            return [];

        return DecompressCore(compressedData.ToArray());
    }

    private static byte[] DecompressCore(byte[] compressedData)
    {
        using var inputStream = new MemoryStream(compressedData, writable: false);
        using var brotliStream = new BrotliStream(inputStream, CompressionMode.Decompress);
        using var outputStream = new MemoryStream();
        
        brotliStream.CopyTo(outputStream);
        return outputStream.ToArray();
    }

    /// <summary>
    /// Decompresses to a rented buffer. Caller must return the buffer to ArrayPool.
    /// Returns the actual decompressed length.
    /// </summary>
    public static (byte[] Buffer, int Length) DecompressToRentedBuffer(ReadOnlySpan<byte> compressedData)
    {
        if (compressedData.IsEmpty)
            return ([], 0);

        // Estimate decompressed size (typically 3-10x compressed for text)
        var estimatedSize = Math.Max(compressedData.Length * 4, DefaultBufferSize);
        var outputBuffer = ArrayPool<byte>.Shared.Rent(estimatedSize);
        
        using var inputStream = new MemoryStream(compressedData.ToArray(), writable: false);
        using var brotliStream = new BrotliStream(inputStream, CompressionMode.Decompress);
        
        var totalRead = 0;
        int bytesRead;
        
        while ((bytesRead = brotliStream.Read(outputBuffer.AsSpan(totalRead))) > 0)
        {
            totalRead += bytesRead;
            
            // Need larger buffer
            if (totalRead >= outputBuffer.Length - DefaultBufferSize)
            {
                var newBuffer = ArrayPool<byte>.Shared.Rent(outputBuffer.Length * 2);
                outputBuffer.AsSpan(0, totalRead).CopyTo(newBuffer);
                ArrayPool<byte>.Shared.Return(outputBuffer);
                outputBuffer = newBuffer;
            }
        }
        
        return (outputBuffer, totalRead);
    }

    #endregion

    #region Utility

    /// <summary>
    /// Checks if the data appears to be Brotli compressed.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsBrotliCompressed(byte[] data)
    {
        if (data == null || data.Length < 4)
            return false;

        var firstByte = data[0];
        
        // If it starts with '{' (0x7B) or '[' (0x5B), it's likely uncompressed JSON
        if (firstByte == 0x7B || firstByte == 0x5B)
            return false;

        // Try to decompress - if it fails, it's not Brotli
        try
        {
            using var inputStream = new MemoryStream(data, 0, Math.Min(data.Length, 64), writable: false);
            using var brotliStream = new BrotliStream(inputStream, CompressionMode.Decompress);
            Span<byte> buffer = stackalloc byte[1];
            return brotliStream.Read(buffer) >= 0;
        }
        catch
        {
            return false;
        }
    }

    #endregion
}
