using System.Reflection;
using AyCode.Core.Extensions;
using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries;

namespace AyCode.Services.SignalRs;

/// <summary>
/// Base interface for SignalR message parameters (metadata).
/// </summary>
public interface ISignalParams { }

/// <summary>
/// SignalR message metadata + optional method parameters.
/// Travels as a separate hub argument (AcBinary serialized).
/// Parameters and data are independent — both can be null or filled in any direction.
///
/// Parameter serialization follows the PostDataJson pattern:
/// - Wire format: Parameters (byte[]) — AcBinary fast-path
/// - Client: SetParameterValues(object[]) packs object[] → byte[][] → byte[]
/// - Server: GetParameterValues(ParameterInfo[]) unpacks byte[] → byte[][] → object[]
/// The protocol never sees byte[][] — only byte[].
/// </summary>
[AcBinarySerializable]
public class SignalParams : ISignalParams
{
    public SignalResponseStatus Status { get; set; }
    public AcSerializerType DataSerializerType { get; set; }

    /// <summary>
    /// Wire format: serialized byte[][] as a single byte[].
    /// AcBinary handles this as byte[] fast-path (zero-copy potential).
    /// Use SetParameterValues/GetParameterValues for typed access.
    /// </summary>
    public byte[]? Parameters { get; set; }

    /// <summary>
    /// Client sets true when requesting raw byte[] (e.g. DataSource populate/merge).
    /// Server: reads this from client's SignalParams → serializes object → byte[] directly.
    /// Protocol: reads this from response SignalParams → skips deserialization, returns raw byte[].
    /// Result: single serialize (server) → single deserialize (consumer). No double ser/deser.
    /// </summary>
    public bool IsRawBytesData { get; set; }

    /// <summary>
    /// Cached deserialized byte[][] from Parameters.
    /// </summary>
    private byte[][]? _parameterValues;

    /// <summary>
    /// Client-side: packs object[] into Parameters (byte[]).
    /// Each parameter is individually binary-serialized via ToBinary().
    ///
    /// PERF: N× ToBinary() = N× context pool acquire/release + N× ArrayBinaryOutput allocation.
    /// For many small primitives (int, bool) this overhead may exceed a single bulk serialization call.
    /// Consider a batch fast-path (single context, write all params) if benchmarks confirm regression.
    /// </summary>
    public void SetParameterValues(object[] parameters)
    {
        // N× pool roundtrip — see PERF note in summary
        var paramBytes = new byte[parameters.Length][];
        for (var i = 0; i < parameters.Length; i++)
        {
            // Pass explicit runtime type — parameters[i] is statically object, so the generic
            // ToBinary<T>() overload would infer T = object and emit an object-typed wire payload
            // (instead of the concrete int/string/DTO type). Explicit null-handling avoids the
            // null-conditional + fallback indirection and produces the Null marker directly.
            var p = parameters[i];
            paramBytes[i] = p == null ? [BinaryTypeCode.Null] : p.ToBinary(p.GetType());
        }

        _parameterValues = paramBytes;
        Parameters = paramBytes.ToBinary();
    }

    /// <summary>
    /// Server-side: unpacks Parameters (byte[]) into typed object[].
    /// Each parameter is deserialized with BinaryTo(targetType) from method signature.
    /// Fills trailing optional parameters with defaults, throws for missing required ones.
    /// Caches the intermediate byte[][] for repeated access.
    ///
    /// NOTE: Currently AcBinary only. JSON parameter deserialization not supported
    /// (would require dispatching on serializer type + AcJsonSerializer reference).
    ///
    /// PERF: Non-generic BinaryTo(Type) uses ThreadLocal + ConcurrentDictionary type cache per call.
    /// N parameters = N× pool roundtrip + N× type-dispatch lookup.
    /// For many small primitives this may be slower than a single bulk deserialization.
    /// Consider a batch fast-path (single context, read all params) if benchmarks confirm regression.
    /// </summary>
    public object[]? GetParameterValues(ParameterInfo[] paramInfos)
    {
        if (paramInfos is not { Length: > 0 })
            return null;

        // Deserialize byte[] → byte[][] (cached)
        _parameterValues ??= Parameters is { Length: > 0 } ? Parameters.BinaryTo<byte[][]>() : null;

        if (_parameterValues is null or { Length: 0 })
            return null;

        // N× pool roundtrip + type-dispatch — see PERF note in summary
        var result = new object?[paramInfos.Length];
        for (var i = 0; i < _parameterValues.Length && i < paramInfos.Length; i++)
            result[i] = _parameterValues[i].BinaryTo(paramInfos[i].ParameterType);

        // Fill trailing optional parameters with defaults
        for (var i = _parameterValues.Length; i < paramInfos.Length; i++)
            result[i] = paramInfos[i].HasDefaultValue ? paramInfos[i].DefaultValue : null;

        return result;
    }
}
