using System.Buffers;
using AyCode.Services.SignalRs;
using Microsoft.AspNetCore.SignalR.Protocol;

namespace AyCode.Services.Server.Tests.SignalRs;

/// <summary>
/// Concurrency / thread-safety stress tests for <see cref="AcBinaryHubProtocol"/> — verify a
/// single shared protocol instance can serve many threads concurrently without per-message state
/// corruption. Regression guard for the per-message header-context race fix (header context is
/// now stack-only on the non-chunked path and per-binder on the chunked path; never on a shared
/// instance field).
///
/// <para>Mirrors the SignalR DI-singleton scenario: one <see cref="AyCodeBinaryHubProtocol"/>
/// instance, many threads invoking <c>WriteMessage</c> + <c>TryParseMessage</c> in parallel with
/// distinct payloads. If the prior <c>_currentHeaderContext</c> race were present, threads would
/// see each other's header context (typically a wrong type AQN) → mismatched parse output, type
/// cast exception, or null where a value is expected.</para>
///
/// <para>The <see cref="TestInvocationBinder"/> declares the data arg as <c>typeof(object)</c>,
/// which forces <see cref="AyCodeBinaryHubProtocol.ReadSingleArgument"/> down the
/// header-context-driven type-resolution path — exactly the path the race used to corrupt.</para>
/// </summary>
[TestClass]
public class AcBinaryHubProtocolConcurrencyTests
{
    [TestMethod]
    public async Task ConcurrentRoundTrip_SharedProtocolInstance_NoStateCorruption()
    {
        // Single shared protocol instance — DI-singleton style, the production-realistic NuGet shape.
        var protocol = new AyCodeBinaryHubProtocol();
        var binder = new TestInvocationBinder();

        const int threadCount = 16;
        const int iterationsPerThread = 200;

        var tasks = new Task[threadCount];
        for (var t = 0; t < threadCount; t++)
        {
            var threadIdx = t;
            tasks[t] = Task.Run(() =>
            {
                for (var i = 0; i < iterationsPerThread; i++)
                {
                    var dataId = threadIdx * 10000 + i;

                    // Distinct payload per (thread, iteration) — race-induced corruption produces
                    // detectable mismatch in the assertions below.
                    var msg = new InvocationMessage("OnReceiveMessage", new object?[]
                    {
                        dataId,                          // arg[0]: int (messageTag)
                        (int?)i,                         // arg[1]: int? (requestId)
                        new SignalParams(),              // arg[2]: SignalParams
                        $"data-t{threadIdx}-i{i}"        // arg[3]: object → string (header-context-typed)
                    });

                    var writer = new ArrayBufferWriter<byte>(8192);
                    protocol.WriteMessage(msg, writer);

                    var seq = new ReadOnlySequence<byte>(writer.WrittenMemory);
                    var success = protocol.TryParseMessage(ref seq, binder, out var parsed);

                    Assert.IsTrue(success, $"thread={threadIdx} iter={i}: TryParseMessage returned false");
                    Assert.IsInstanceOfType<InvocationMessage>(parsed,
                        $"thread={threadIdx} iter={i}: parsed message wrong type");

                    var inv = (InvocationMessage)parsed!;
                    Assert.AreEqual(4, inv.Arguments.Length,
                        $"thread={threadIdx} iter={i}: argument count mismatch");
                    Assert.AreEqual(dataId, (int)inv.Arguments[0]!,
                        $"thread={threadIdx} iter={i}: arg[0] (messageTag) mismatch — possible race");
                    Assert.AreEqual(i, (int?)inv.Arguments[1],
                        $"thread={threadIdx} iter={i}: arg[1] (requestId) mismatch");
                    Assert.IsInstanceOfType<SignalParams>(inv.Arguments[2],
                        $"thread={threadIdx} iter={i}: arg[2] not SignalParams — header-context race?");
                    Assert.AreEqual($"data-t{threadIdx}-i{i}", inv.Arguments[3] as string,
                        $"thread={threadIdx} iter={i}: arg[3] (data) mismatch — header-context race?");
                }
            });
        }

        await Task.WhenAll(tasks);
    }

    [TestMethod]
    public async Task ConcurrentRoundTrip_VariedPayloadTypes_HeaderContextResolvedPerMessage()
    {
        // Stresses the header-context Type-resolution path more aggressively: each iteration
        // alternates between distinct payload types (string / int / int[]). A header-context
        // race would resolve the wrong type AQN → InvalidCastException or silent type-mismatch.
        var protocol = new AyCodeBinaryHubProtocol();
        var binder = new TestInvocationBinder();

        const int threadCount = 8;
        const int iterationsPerThread = 150;

        var tasks = new Task[threadCount];
        for (var t = 0; t < threadCount; t++)
        {
            var threadIdx = t;
            tasks[t] = Task.Run(() =>
            {
                for (var i = 0; i < iterationsPerThread; i++)
                {
                    var dataId = threadIdx * 10000 + i;
                    object data = (i % 3) switch
                    {
                        0 => $"str-t{threadIdx}-i{i}",
                        1 => dataId,
                        _ => new[] { dataId, threadIdx, i }
                    };

                    var msg = new InvocationMessage("OnReceiveMessage", new object?[]
                    {
                        dataId, (int?)i, new SignalParams(), data
                    });

                    var writer = new ArrayBufferWriter<byte>(8192);
                    protocol.WriteMessage(msg, writer);

                    var seq = new ReadOnlySequence<byte>(writer.WrittenMemory);
                    Assert.IsTrue(protocol.TryParseMessage(ref seq, binder, out var parsed),
                        $"thread={threadIdx} iter={i}: TryParseMessage returned false");

                    var inv = (InvocationMessage)parsed!;
                    Assert.AreEqual(dataId, (int)inv.Arguments[0]!,
                        $"thread={threadIdx} iter={i}: messageTag mismatch");

                    // Validate the header-context-driven type was resolved correctly per-message.
                    switch (i % 3)
                    {
                        case 0:
                            Assert.AreEqual($"str-t{threadIdx}-i{i}", inv.Arguments[3] as string,
                                $"thread={threadIdx} iter={i}: string payload mismatch — header-context race?");
                            break;
                        case 1:
                            Assert.AreEqual(dataId, (int)inv.Arguments[3]!,
                                $"thread={threadIdx} iter={i}: int payload mismatch — header-context race?");
                            break;
                        default:
                            var arr = inv.Arguments[3] as int[];
                            Assert.IsNotNull(arr,
                                $"thread={threadIdx} iter={i}: int[] payload null — header-context race?");
                            Assert.AreEqual(3, arr.Length);
                            Assert.AreEqual(dataId, arr[0]);
                            break;
                    }
                }
            });
        }

        await Task.WhenAll(tasks);
    }
}
