using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using AyCode.Core.Compression;
using AyCode.Core.Extensions;
using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;

namespace AyCode.Services.SignalRs;

/// <summary>
/// Centralized helper for SignalR serialization operations.
/// Provides optimized primitives for JSON/Binary serialization with pooled buffers.
/// </summary>
public static class SignalRSerializationHelper
{
    // Pre-boxed boolean values to avoid repeated boxing
    private static readonly string JsonTrue = "true";
    private static readonly string JsonFalse = "false";

    #region Primitive JSON Serialization

    /// <summary>
    /// Serialize a primitive value to JSON string with minimal overhead.
    /// Falls back to full JSON serialization for complex types.
    /// </summary>
    [Obsolete("Use direct object[] binary serialization instead of JSON-encoded primitives.")]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static string SerializePrimitiveToJson(object value)
    {
        return value switch
        {
            int i => i.ToString(),
            long l => l.ToString(),
            Guid g => SerializeGuidToJson(g),
            bool b => b ? JsonTrue : JsonFalse,
            // Strings need proper JSON escaping for special characters
            string => value.ToJson(),
            _ => value.ToJson()
        };
    }

    /// <summary>
    /// Serialize a Guid to JSON string with pre-allocated buffer.
    /// </summary>
    [Obsolete("Use direct object[] binary serialization instead of JSON-encoded Guids.")]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static string SerializeGuidToJson(Guid g)
    {
        // Pre-allocate exact size: 38 chars = 2 quotes + 36 guid chars
        return string.Create(38, g, static (span, guid) =>
        {
            span[0] = '"';
            guid.TryFormat(span[1..], out _);
            span[37] = '"';
        });
    }

    #endregion

    #region Binary Serialization

    /// <summary>
    /// Serialize object to binary via the pool-backed byte[]-output overload (1 final allocation).
    /// Lighter than the IBufferWriter path (2 allocations: ArrayBufferWriter + buffer + ToArray copy).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte[] SerializeToBinary<T>(T value, AcBinarySerializerOptions? options = null)
        => value.ToBinary(options ?? AcBinarySerializerOptions.Default);

    /// <summary>
    /// Serialize object to binary and write to existing ArrayBufferWriter. Caller owns the writer
    /// — useful for buffer-reuse scenarios where the same writer accepts multiple writes.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void SerializeToBinary<T>(T value, ArrayBufferWriter<byte> writer, AcBinarySerializerOptions? options = null)
    {
        value.ToBinary(writer, options ?? AcBinarySerializerOptions.Default);
    }

    /// <summary>
    /// Serialize object to binary with explicit runtime type. Use this overload at heterogeneous
    /// <c>object?</c> call sites where the generic <see cref="SerializeToBinary{T}(T, AcBinarySerializerOptions?)"/>
    /// would infer <c>T = object</c> and emit an object-typed wire payload instead of the concrete
    /// runtime type's encoding. Typical use: <c>SerializeToBinary(value, value.GetType())</c>.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte[] SerializeToBinary(object? value, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, AcBinarySerializerOptions? options = null)
        => value.ToBinary(type, options ?? AcBinarySerializerOptions.Default);

    /// <summary>
    /// Deserialize binary data to object.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T? DeserializeFromBinary<T>(byte[] data)
    {
        return data.BinaryTo<T>();
    }

    #endregion

    #region JSON Serialization with Brotli

    /// <summary>
    /// Serialize object to JSON and compress with Brotli.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte[] SerializeToCompressedJson<T>(T value, AcJsonSerializerOptions? options = null)
    {
        var json = value.ToJson(options ?? AcJsonSerializerOptions.Default);
        return GzipHelper.Compress(json);
    }

    /// <summary>
    /// Decompress Brotli data and deserialize JSON to object.
    /// Uses pooled buffer for decompression.
    /// </summary>
    public static T? DeserializeFromCompressedJson<T>(byte[] compressedData)
    {
        var (buffer, length) = GzipHelper.DecompressToRentedBuffer(compressedData.AsSpan());
        try
        {
            return AcJsonDeserializer.Deserialize<T>(new ReadOnlySpan<byte>(buffer, 0, length));
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }

    /// <summary>
    /// Decompress Brotli data to rented buffer for direct processing.
    /// Caller must return buffer to ArrayPool.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static (byte[] Buffer, int Length) DecompressToRentedBuffer(byte[] compressedData)
    {
        return GzipHelper.DecompressToRentedBuffer(compressedData.AsSpan());
    }

    #endregion

    #region Response Data Helpers

    /// <summary>
    /// Check if string appears to be valid JSON (starts with { or [).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsValidJsonString(ReadOnlySpan<char> text)
    {
        var trimmed = text.Trim();
        return trimmed.Length > 1 && 
               (trimmed[0] == '{' || trimmed[0] == '[') && 
               (trimmed[^1] == '}' || trimmed[^1] == ']');
    }

    /// <summary>
    /// Create response binary data based on serializer type.
    /// </summary>
    public static byte[]? CreateResponseData(object? responseData, AcSerializerOptions serializerOptions)
    {
        if (responseData == null)
            return null;

        if (serializerOptions.SerializerType == AcSerializerType.Binary)
        {
            if (responseData is byte[] byteData)
                return byteData;

            var binaryOptions = serializerOptions as AcBinarySerializerOptions ?? AcBinarySerializerOptions.Default;
            // Explicit runtime type — responseData is statically object?, so the generic
            // SerializeToBinary<T>(T) overload would infer T = object and emit object-typed bytes.
            return SerializeToBinary(responseData, responseData.GetType(), binaryOptions);
        }

        // JSON mode with Brotli compression
        string json;
        if (responseData is string strData && IsValidJsonString(strData.AsSpan()))
        {
            json = strData;
        }
        else
        {
            var jsonOptions = serializerOptions as AcJsonSerializerOptions ?? AcJsonSerializerOptions.Default;
            json = responseData.ToJson(jsonOptions);
        }

        return GzipHelper.Compress(json);
    }

    #endregion
}
