using System.Buffers;
using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Tests.TestModels;
using AyCode.Services.Server.SignalRs;
using AyCode.Services.SignalRs;
using MessagePack.Resolvers;
using AyCode.Core.Tests.Serialization;
using AyCode.Core.Serializers;
using Microsoft.AspNetCore.SignalR.Protocol;

namespace AyCode.Services.Server.Tests.SignalRs;

/// <summary>
/// Base class for SignalR client-to-hub communication tests.
/// Tests the full round-trip: Client -> Server -> Service -> Response -> Client
/// Derived classes specify the serializer type (JSON or Binary).
/// </summary>
public abstract class SignalRClientToHubTestBase
{
    protected abstract AcSerializerOptions SerializerOption { get; }
    
    protected TestLogger _logger = null!;
    protected TestableSignalRClient2 _client = null!;
    protected TestableSignalRHub2 _hub = null!;
    protected TestSignalRService2 _service = null!;

    [TestInitialize]
    public void Setup()
    {
        _logger = new TestLogger();
        _hub = new TestableSignalRHub2();
        _service = new TestSignalRService2();
        _client = new TestableSignalRClient2(_hub, _logger);

        _hub.SetSerializerType(SerializerOption);
        _hub.RegisterService(_service, _client);
    }

    #region Primitive Parameter Tests

    [TestMethod]
    [DataRow(42)]
    [DataRow(0)]
    [DataRow(-100)]
    [DataRow(int.MaxValue)]
    public async Task Post_SingleInt_ReturnsStringRepresentation(int value)
    {
        var result = await _client.PostDataAsync<int, string>(TestSignalRTags.SingleIntParam, value);
        Assert.AreEqual(value.ToString(), result);
    }

    [TestMethod]
    [DataRow(10, 20, 30)]
    [DataRow(0, 0, 0)]
    [DataRow(-5, 10, 5)]
    public async Task Post_TwoInts_ReturnsSum(int a, int b, int expectedSum)
    {
        var result = await _client.PostAsync<int>(TestSignalRTags.TwoIntParams, [a, b]);
        Assert.AreEqual(expectedSum, result);
    }

    [TestMethod]
    [DataRow(true)]
    [DataRow(false)]
    public async Task Post_Bool_ReturnsSameValue(bool value)
    {
        var result = await _client.PostDataAsync<bool, bool>(TestSignalRTags.BoolParam, value);
        Assert.AreEqual(value, result);
    }

    [TestMethod]
    [DataRow("Hello")]
    [DataRow("")]
    [DataRow("Special chars: <>\"'&")]
    public async Task Post_String_ReturnsEcho(string text)
    {
        var result = await _client.PostDataAsync<string, string>(TestSignalRTags.StringParam, text);
        Assert.AreEqual($"Echo: {text}", result);
    }

    [TestMethod]
    public async Task Post_Guid_ReturnsSameValue()
    {
        var guid = Guid.NewGuid();
        var result = await _client.PostDataAsync<Guid, Guid>(TestSignalRTags.GuidParam, guid);
        Assert.AreEqual(guid, result);
    }

    [TestMethod]
    public async Task Post_GuidEmpty_ReturnsSameValue()
    {
        var result = await _client.PostDataAsync<Guid, Guid>(TestSignalRTags.GuidParam, Guid.Empty);
        Assert.AreEqual(Guid.Empty, result);
    }

    [TestMethod]
    [DataRow(TestStatus.Pending, TestStatus.Pending)]
    [DataRow(TestStatus.Processing, TestStatus.Processing)]
    [DataRow(TestStatus.Completed, TestStatus.Completed)]
    public async Task Post_Enum_ReturnsSameValue(TestStatus input, TestStatus expected)
    {
        var result = await _client.PostDataAsync<TestStatus, TestStatus>(TestSignalRTags.EnumParam, input);
        Assert.AreEqual(expected, result);
    }

    [TestMethod]
    public async Task Get_NoParams_ReturnsOK()
    {
        var result = await _client.GetAllAsync<string>(TestSignalRTags.NoParams);
        Assert.AreEqual("OK", result);
    }

    [TestMethod]
    public async Task Post_MultipleTypes_ReturnsConcatenated()
    {
        var result = await _client.PostAsync<string>(TestSignalRTags.MultipleTypesParams, [true, "test", 42]);
        Assert.AreEqual("True-test-42", result);
    }

    [TestMethod]
    [DataRow(123.45)]
    [DataRow(0.0)]
    [DataRow(-99.99)]
    public async Task Post_Decimal_ReturnsDoubled(double inputDouble)
    {
        var input = (decimal)inputDouble;
        var result = await _client.PostDataAsync<decimal, decimal>(TestSignalRTags.DecimalParam, input);
        Assert.AreEqual(input * 2, result);
    }

    [TestMethod]
    public async Task Post_DateTime_ReturnsSameValue()
    {
        var dateTime = new DateTime(2024, 6, 15, 10, 30, 45, DateTimeKind.Utc);
        var result = await _client.PostDataAsync<DateTime, DateTime>(TestSignalRTags.DateTimeParam, dateTime);
        Assert.AreEqual(dateTime, result);
    }

    [TestMethod]
    [DataRow(3.14159)]
    [DataRow(0.0)]
    [DataRow(-1.5)]
    public async Task Post_Double_ReturnsSameValue(double value)
    {
        var result = await _client.PostDataAsync<double, double>(TestSignalRTags.DoubleParam, value);
        Assert.AreEqual(value, result, 0.0001);
    }

    [TestMethod]
    [DataRow(9223372036854775807L)]
    [DataRow(0L)]
    [DataRow(-1L)]
    public async Task Post_Long_ReturnsSameValue(long value)
    {
        var result = await _client.PostDataAsync<long, long>(TestSignalRTags.LongParam, value);
        Assert.AreEqual(value, result);
    }

    #endregion

    #region Complex Object Tests

    [TestMethod]
    public async Task Post_TestOrderItem_ReturnsProcessedItem()
    {
        var item = new TestOrderItem_All_True { Id = 1, ProductName = "Widget", Quantity = 5, UnitPrice = 10.50m };

        var result = await _client.PostDataAsync(TestSignalRTags.TestOrderItemParam, item);

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual("Processed: Widget", result.ProductName);
        Assert.AreEqual(item.Quantity * 2, result.Quantity);
        Assert.AreEqual(item.UnitPrice * 2, result.UnitPrice);
    }

    [TestMethod]
    public async Task Post_TestOrder_ReturnsSameOrder()
    {
        var order = TestDataFactory.CreateOrder(itemCount: 2);

        var result = await _client.PostDataAsync<TestOrder_All_True, TestOrder_All_True>(TestSignalRTags.TestOrderParam, order);

        Assert.IsNotNull(result);
        Assert.AreEqual(order.Id, result.Id);
        Assert.AreEqual(order.OrderNumber, result.OrderNumber);
        Assert.AreEqual(2, result.Items.Count);
    }

    [TestMethod]
    public async Task Post_SharedTag_ReturnsSameTag()
    {
        var tag = new SharedTag_All_True { Id = 1, Name = "Important", Color = "#FF0000" };

        var result = await _client.PostDataAsync<SharedTag_All_True, SharedTag_All_True>(TestSignalRTags.SharedTagParam, tag);

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual("Important", result.Name);
        Assert.AreEqual("#FF0000", result.Color);
    }

    #endregion

    #region Collection Parameter Tests

    [TestMethod]
    public async Task Post_IntArray_ReturnsDoubledValues()
    {
        var input = new[] { 1, 2, 3, 4, 5 };

        var result = await _client.PostDataAsync<int[], int[]>(TestSignalRTags.IntArrayParam, input);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(new[] { 2, 4, 6, 8, 10 }, result);
    }

    [TestMethod]
    public async Task Post_GuidArray_ReturnsSameValues()
    {
        var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };

        var result = await _client.PostDataAsync<Guid[], Guid[]>(TestSignalRTags.GuidArrayParam, guids);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(guids, result);
    }

    [TestMethod]
    public async Task Post_StringList_ReturnsUppercased()
    {
        var input = new List<string> { "apple", "banana", "cherry" };

        var result = await _client.PostDataAsync<List<string>, List<string>>(TestSignalRTags.StringListParam, input);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(new List<string> { "APPLE", "BANANA", "CHERRY" }, result);
    }

    [TestMethod]
    public async Task Post_TestOrderItemList_ReturnsSameItems()
    {
        var items = new List<TestOrderItem_All_True>
        {
            new() { Id = 1, ProductName = "Item1" },
            new() { Id = 2, ProductName = "Item2" }
        };

        var result = await _client.PostDataAsync<List<TestOrderItem_All_True>, List<TestOrderItem_All_True>>(TestSignalRTags.TestOrderItemListParam, items);

        Assert.IsNotNull(result);
        Assert.AreEqual(2, result.Count);
        Assert.AreEqual("Item1", result[0].ProductName);
        Assert.AreEqual("Item2", result[1].ProductName);
    }

    [TestMethod]
    public async Task Post_IntList_ReturnsDoubledValues()
    {
        var input = new List<int> { 10, 20, 30 };

        var result = await _client.PostDataAsync<List<int>, List<int>>(TestSignalRTags.IntListParam, input);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(new List<int> { 20, 40, 60 }, result);
    }

    [TestMethod]
    public async Task Post_BoolArray_ReturnsSameValues()
    {
        var input = new[] { true, false, true, false };

        var result = await _client.PostDataAsync<bool[], bool[]>(TestSignalRTags.BoolArrayParam, input);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(input, result);
    }

    [TestMethod]
    public async Task Post_MixedWithArray_ReturnsFormattedString()
    {
        var numbers = new[] { 1, 2, 3 };

        var result = await _client.PostAsync<string>(TestSignalRTags.MixedWithArrayParam, [true, numbers, "end"]);

        Assert.AreEqual("True-[1,2,3]-end", result);
    }

    [TestMethod]
    public async Task Post_NestedList_ReturnsSameStructure()
    {
        var nested = new List<List<int>>
        {
            new() { 1, 2, 3 },
            new() { 4, 5 },
            new() { 6 }
        };

        var result = await _client.PostDataAsync<List<List<int>>, List<List<int>>>(TestSignalRTags.NestedListParam, nested);

        Assert.IsNotNull(result);
        Assert.AreEqual(3, result.Count);
        CollectionAssert.AreEqual(new List<int> { 1, 2, 3 }, result[0]);
        CollectionAssert.AreEqual(new List<int> { 4, 5 }, result[1]);
        CollectionAssert.AreEqual(new List<int> { 6 }, result[2]);
    }

    #endregion

    #region Extended Array Tests

    [TestMethod]
    public async Task Post_LongArray_ReturnsSameValues()
    {
        var input = new[] { 1L, 2L, long.MaxValue };

        var result = await _client.PostDataAsync<long[], long[]>(TestSignalRTags.LongArrayParam, input);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(input, result);
    }

    [TestMethod]
    public async Task GetAll_WithEmptyArrayContextParam_ReturnsResult()
    {
        var result = await _client.GetAllAsync<int[]>(TestSignalRTags.IntArrayParam, [Array.Empty<int>()]);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual(0, result.Length, "Empty array should return empty array");
    }

    [TestMethod]
    public async Task GetAll_WithArrayContextParam_ReturnsDoubledValues()
    {
        var input = new[] { 1, 2, 3 };
        var result = await _client.GetAllAsync<int[]>(TestSignalRTags.IntArrayParam, [input]);

        Assert.IsNotNull(result, "Result should not be null");
        CollectionAssert.AreEqual(new[] { 2, 4, 6 }, result);
    }

    [TestMethod]
    public async Task Post_DecimalArray_ReturnsSameValues()
    {
        var input = new[] { 1.1m, 2.2m, 3.3m };

        var result = await _client.PostDataAsync<decimal[], decimal[]>(TestSignalRTags.DecimalArrayParam, input);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(input, result);
    }

    [TestMethod]
    public async Task Post_DateTimeArray_ReturnsSameValues()
    {
        var input = new[]
        {
            new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            new DateTime(2024, 6, 15, 12, 30, 0, DateTimeKind.Utc)
        };

        var result = await _client.PostDataAsync<DateTime[], DateTime[]>(TestSignalRTags.DateTimeArrayParam, input);

        Assert.IsNotNull(result);
        Assert.AreEqual(2, result.Length);
        Assert.AreEqual(input[0], result[0]);
        Assert.AreEqual(input[1], result[1]);
    }

    [TestMethod]
    public async Task Post_EnumArray_ReturnsSameValues()
    {
        var input = new[] { TestStatus.Pending, TestStatus.Completed, TestStatus.Processing };

        var result = await _client.PostDataAsync<TestStatus[], TestStatus[]>(TestSignalRTags.EnumArrayParam, input);

        Assert.IsNotNull(result);
        CollectionAssert.AreEqual(input, result);
    }

    [TestMethod]
    public async Task Post_DoubleArray_ReturnsSameValues()
    {
        var input = new[] { 1.1, 2.2, 3.3 };

        var result = await _client.PostDataAsync<double[], double[]>(TestSignalRTags.DoubleArrayParam, input);

        Assert.IsNotNull(result);
        Assert.AreEqual(3, result.Length);
        Assert.AreEqual(1.1, result[0], 0.001);
        Assert.AreEqual(2.2, result[1], 0.001);
        Assert.AreEqual(3.3, result[2], 0.001);
    }

    [TestMethod]
    public async Task Post_SharedTagArray_ReturnsSameValues()
    {
        var input = new[]
        {
            new SharedTag_All_True { Id = 1, Name = "Tag1", Color = "#FF0000" },
            new SharedTag_All_True { Id = 2, Name = "Tag2", Color = "#00FF00" }
        };

        var result = await _client.PostDataAsync<SharedTag_All_True[], SharedTag_All_True[]>(TestSignalRTags.SharedTagArrayParam, input);

        Assert.IsNotNull(result);
        Assert.AreEqual(2, result.Length);
        Assert.AreEqual("Tag1", result[0].Name);
        Assert.AreEqual("Tag2", result[1].Name);
    }

    [TestMethod]
    public async Task Post_Dictionary_ReturnsSameValues()
    {
        var input = new Dictionary<string, int>
        {
            { "one", 1 },
            { "two", 2 },
            { "three", 3 }
        };

        var result = await _client.PostDataAsync<Dictionary<string, int>, Dictionary<string, int>>(TestSignalRTags.DictionaryParam, input);

        Assert.IsNotNull(result);
        Assert.AreEqual(3, result.Count);
        Assert.AreEqual(1, result["one"]);
        Assert.AreEqual(2, result["two"]);
        Assert.AreEqual(3, result["three"]);
    }

    #endregion

    #region Mixed Parameter Tests

    [TestMethod]
    public async Task Post_IntAndDto_ReturnsFormattedString()
    {
        var item = new TestOrderItem_All_True { Id = 1, ProductName = "Widget" };

        var result = await _client.PostAsync<string>(TestSignalRTags.IntAndDtoParam, [42, item]);

        Assert.AreEqual("42-Widget", result);
    }

    [TestMethod]
    public async Task Post_DtoAndList_ReturnsFormattedString()
    {
        var item = new TestOrderItem_All_True { Id = 1, ProductName = "Product" };
        var numbers = new List<int> { 1, 2, 3 };

        var result = await _client.PostAsync<string>(TestSignalRTags.DtoAndListParam, [item, numbers]);

        Assert.AreEqual("Product-[1,2,3]", result);
    }

    [TestMethod]
    public async Task Post_ThreeComplexParams_ReturnsFormattedString()
    {
        var item = new TestOrderItem_All_True { Id = 1, ProductName = "Item" };
        var tags = new List<string> { "tag1", "tag2", "tag3" };
        var sharedTag = new SharedTag_All_True { Id = 1, Name = "Shared" };

        var result = await _client.PostAsync<string>(TestSignalRTags.ThreeComplexParams, [item, tags, sharedTag]);

        Assert.AreEqual("Item-3-Shared", result);
    }

    [TestMethod]
    public async Task Post_FiveParams_ReturnsFormattedString()
    {
        var guid = Guid.Parse("12345678-1234-1234-1234-123456789abc");
        
        var result = await _client.PostAsync<string>(TestSignalRTags.FiveParams, [42, "hello", true, guid, 99.99m]);
        
        Assert.IsNotNull(result);
        Assert.AreEqual($"42-hello-True-{guid}-99.99", result);
    }

    [TestMethod]
    public async Task GetByIdAsync_FiveParams_ReturnsFormattedString()
    {
        var guid = Guid.NewGuid();

        var result = await _client.GetByIdAsync<string>(TestSignalRTags.FiveParams, [1, "text", true, guid, 99.99m]);

        Assert.IsNotNull(result);
        Assert.AreEqual($"1-text-True-{guid}-99.99", result);
    }

    #endregion

    #region Error Handling Tests

    [TestMethod]
    public async Task Post_ThrowsException_ReturnsError()
    {
        var result = await _client.GetAllAsync<string>(TestSignalRTags.ThrowsException);

        Assert.IsNull(result);
        Assert.IsTrue(_logger.HasErrorLogs);
    }

    #endregion

    #region Async Task<T> Method Tests

    [TestMethod]
    public async Task Async_TestOrderItem_ReturnsProcessedItem()
    {
        var item = new TestOrderItem_All_True { Id = 1, ProductName = "Widget", Quantity = 5, UnitPrice = 10.50m };

        var result = await _client.PostDataAsync(TestSignalRTags.AsyncTestOrderItemParam, item);

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual("Async: Widget", result.ProductName);
        Assert.AreEqual(item.Quantity * 3, result.Quantity);
        Assert.AreEqual(item.UnitPrice * 3, result.UnitPrice);
    }

    [TestMethod]
    public async Task Async_String_ReturnsProcessedString()
    {
        var result = await _client.PostDataAsync<string, string>(TestSignalRTags.AsyncStringParam, "TestInput");

        Assert.IsNotNull(result);
        Assert.AreEqual("Async: TestInput", result);
    }

    [TestMethod]
    public async Task Async_NoParams_ReturnsAsyncOK()
    {
        var result = await _client.GetAllAsync<string>(TestSignalRTags.AsyncNoParams);

        Assert.IsNotNull(result);
        Assert.AreEqual("AsyncOK", result);
    }

    [TestMethod]
    public async Task Async_Int_ReturnsDoubledValue()
    {
        var result = await _client.PostDataAsync<int, int>(TestSignalRTags.AsyncIntParam, 42);

        Assert.AreEqual(84, result);
    }

    #endregion

    #region Round-Trip Integrity Tests

    [TestMethod]
    public async Task RoundTrip_ComplexObject_PreservesAllProperties()
    {
        var item = new TestOrderItem_All_True 
        { 
            Id = 999, 
            ProductName = "RoundTrip Test Item", 
            Quantity = 50, 
            UnitPrice = 123.45m,
            Status = TestStatus.Processing
        };

        var result = await _client.PostDataAsync(TestSignalRTags.TestOrderItemParam, item);

        Assert.IsNotNull(result);
        Assert.AreEqual(999, result.Id);
        Assert.AreEqual("Processed: RoundTrip Test Item", result.ProductName);
        Assert.AreEqual(100, result.Quantity);
        Assert.AreEqual(246.90m, result.UnitPrice);
    }

    [TestMethod]
    public async Task RoundTrip_NestedOrder_PreservesHierarchy()
    {
        var order = TestDataFactory.CreateOrder(itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 1);

        var result = await _client.PostDataAsync<TestOrder_All_True, TestOrder_All_True>(TestSignalRTags.TestOrderParam, order);

        Assert.IsNotNull(result);
        Assert.AreEqual(order.Id, result.Id);
        Assert.AreEqual(order.OrderNumber, result.OrderNumber);
        Assert.AreEqual(3, result.Items.Count);
        
        for (int i = 0; i < 3; i++)
        {
            Assert.AreEqual(order.Items[i].Id, result.Items[i].Id);
            Assert.AreEqual(2, result.Items[i].Pallets.Count);
        }
    }

    [TestMethod]
    public async Task RoundTrip_SpecialCharacters_PreservedCorrectly()
    {
        var testString = "Special: \"quotes\" 'apostrophes' <brackets> & ampersand \\ backslash \n newline";
        
        var result = await _client.PostDataAsync<string, string>(TestSignalRTags.StringParam, testString);

        Assert.IsNotNull(result);
        Assert.AreEqual($"Echo: {testString}", result);
    }

    [TestMethod]
    public async Task RoundTrip_UnicodeCharacters_PreservedCorrectly()
    {
        var testString = "Unicode: 中文 日本語 한국어 🎉 émoji";
        
        var result = await _client.PostDataAsync<string, string>(TestSignalRTags.StringParam, testString);

        Assert.IsNotNull(result);
        Assert.AreEqual($"Echo: {testString}", result);
    }

    [TestMethod]
    public async Task RoundTrip_EmptyString_PreservedCorrectly()
    {
        var result = await _client.PostDataAsync<string, string>(TestSignalRTags.StringParam, "");

        Assert.IsNotNull(result);
        Assert.AreEqual("Echo: ", result);
    }

    [TestMethod]
    public async Task RoundTrip_LargeDecimal_PreservedCorrectly()
    {
        var largeDecimal = 999999999999.999999m;
        
        var result = await _client.PostDataAsync<decimal, decimal>(TestSignalRTags.DecimalParam, largeDecimal);

        Assert.AreEqual(largeDecimal * 2, result);
    }

    [TestMethod]
    public async Task RoundTrip_ExtremeInt_PreservedCorrectly()
    {
        var result = await _client.PostDataAsync<int, string>(TestSignalRTags.SingleIntParam, int.MaxValue);

        Assert.AreEqual(int.MaxValue.ToString(), result);
    }

    #endregion

    #region Response Data Integrity Tests

    [TestMethod]
    public async Task ResponseData_NotDoubleEscaped()
    {
        var item = new TestOrderItem_All_True { Id = 1, ProductName = "Test", Quantity = 10, UnitPrice = 20m };

        var result = await _client.PostDataAsync(TestSignalRTags.TestOrderItemParam, item);

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual("Processed: Test", result.ProductName);
        Assert.AreEqual(20, result.Quantity);
        Assert.AreEqual(40m, result.UnitPrice);
    }

    [TestMethod]
    public async Task CollectionResponse_DeserializesCorrectly()
    {
        var items = new List<TestOrderItem_All_True>
        {
            new() { Id = 1, ProductName = "Item1", Quantity = 10, UnitPrice = 1.1m },
            new() { Id = 2, ProductName = "Item2", Quantity = 20, UnitPrice = 2.2m },
            new() { Id = 3, ProductName = "Item3", Quantity = 30, UnitPrice = 3.3m }
        };

        var result = await _client.PostDataAsync<List<TestOrderItem_All_True>, List<TestOrderItem_All_True>>(
            TestSignalRTags.TestOrderItemListParam, items);

        Assert.IsNotNull(result);
        Assert.AreEqual(3, result.Count);
        
        for (int i = 0; i < 3; i++)
        {
            Assert.AreEqual(items[i].Id, result[i].Id);
            Assert.AreEqual(items[i].ProductName, result[i].ProductName);
        }
    }

    [TestMethod]
    public async Task Async_Method_ReturnsActualResult_NotTaskWrapper()
    {
        var item = new TestOrderItem_All_True { Id = 42, ProductName = "TestProduct", Quantity = 5, UnitPrice = 10m };

        var result = await _client.PostDataAsync(TestSignalRTags.AsyncTestOrderItemParam, item);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual(42, result.Id, "Id should match the original");
        Assert.AreEqual("Async: TestProduct", result.ProductName, 
            "ProductName should be 'Async: TestProduct', not null.");
        Assert.AreEqual(15, result.Quantity, "Quantity should be tripled (5*3=15).");
        Assert.AreEqual(30m, result.UnitPrice, "UnitPrice should be tripled (10*3=30).");
    }

    #endregion

    #region Task.FromResult Integration Tests

    [TestMethod]
    public async Task TaskFromResult_String_ReturnsActualResult_NotTaskWrapper()
    {
        var result = await _client.PostDataAsync<string, string>(TestSignalRTags.TaskFromResultStringParam, "TestInput");

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual("FromResult: TestInput", result, 
            "Should return the actual string, not Task wrapper JSON");
    }

    [TestMethod]
    public async Task TaskFromResult_ComplexObject_ReturnsActualResult_NotTaskWrapper()
    {
        var item = new TestOrderItem_All_True { Id = 42, ProductName = "TestProduct", Quantity = 5, UnitPrice = 10m };

        var result = await _client.PostDataAsync(TestSignalRTags.TaskFromResultTestOrderItemParam, item);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual(42, result.Id, "Id should match the original");
        Assert.AreEqual("FromResult: TestProduct", result.ProductName, 
            "ProductName should be 'FromResult: TestProduct'.");
        Assert.AreEqual(10, result.Quantity, "Quantity should be doubled (5*2=10).");
        Assert.AreEqual(20m, result.UnitPrice, "UnitPrice should be doubled (10*2=20).");
    }

    [TestMethod]
    public async Task TaskFromResult_Int_ReturnsActualResult_NotTaskWrapper()
    {
        var result = await _client.PostDataAsync<int, int>(TestSignalRTags.TaskFromResultIntParam, 42);

        Assert.AreEqual(84, result, "Should return doubled value (42*2=84).");
    }

    [TestMethod]
    public async Task TaskFromResult_NoResult_CompletesSuccessfully()
    {
        var result = await _client.GetAllAsync<object>(TestSignalRTags.TaskFromResultNoParams);
    }

    #endregion

    #region InvokeMethod Unit Tests

    [TestMethod]
    public void InvokeMethod_SyncMethod_ReturnsValue()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleSingleInt")!;
        var result = methodInfo.InvokeMethod(service, 42);
        
        Assert.AreEqual("42", result);
    }

    [TestMethod]
    public void InvokeMethod_AsyncTaskTMethod_ReturnsUnwrappedValue()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleAsyncString")!;
        
        var result = methodInfo.InvokeMethod(service, "Test");
        
        Assert.IsNotNull(result, "InvokeMethod should unwrap Task<T> and return the result");
        Assert.AreEqual("Async: Test", result);
    }

    [TestMethod]
    public void InvokeMethod_AsyncTaskTMethod_WithComplexObject_ReturnsUnwrappedValue()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleAsyncTestOrderItem")!;
        var input = new TestOrderItem_All_True { Id = 1, ProductName = "Widget", Quantity = 5, UnitPrice = 10m };
        
        var result = methodInfo.InvokeMethod(service, input);
        
        Assert.IsNotNull(result, "InvokeMethod should unwrap Task<TestOrderItem_All_True> and return the result");
        Assert.IsInstanceOfType(result, typeof(TestOrderItem_All_True));
        var item = (TestOrderItem_All_True)result;
        Assert.AreEqual("Async: Widget", item.ProductName);
        Assert.AreEqual(15, item.Quantity);
    }

    [TestMethod]
    public void InvokeMethod_AsyncTaskTMethod_WithInt_ReturnsUnwrappedValue()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleAsyncInt")!;
        
        var result = methodInfo.InvokeMethod(service, 42);
        
        Assert.IsNotNull(result, "InvokeMethod should unwrap Task<int> and return the result");
        Assert.AreEqual(84, result);
    }

    [TestMethod]
    public void InvokeMethod_TaskFromResult_ReturnsUnwrappedValue()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleTaskFromResultString")!;
        
        var result = methodInfo.InvokeMethod(service, "Test");
        
        Assert.IsNotNull(result, "InvokeMethod should unwrap Task.FromResult<T> and return the result");
        Assert.AreEqual("FromResult: Test", result);
    }

    [TestMethod]
    public void InvokeMethod_TaskFromResult_WithComplexObject_ReturnsUnwrappedValue()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleTaskFromResultTestOrderItem")!;
        var input = new TestOrderItem_All_True { Id = 1, ProductName = "Widget", Quantity = 5, UnitPrice = 10m };
        
        var result = methodInfo.InvokeMethod(service, input);
        
        Assert.IsNotNull(result, "InvokeMethod should unwrap Task.FromResult<TestOrderItem_All_True> and return the result");
        Assert.IsInstanceOfType(result, typeof(TestOrderItem_All_True));
        var item = (TestOrderItem_All_True)result;
        Assert.AreEqual("FromResult: Widget", item.ProductName);
        Assert.AreEqual(10, item.Quantity);
    }

    [TestMethod]
    public void InvokeMethod_TaskFromResult_WithInt_ReturnsUnwrappedValue()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleTaskFromResultInt")!;
        
        var result = methodInfo.InvokeMethod(service, 42);
        
        Assert.IsNotNull(result, "InvokeMethod should unwrap Task.FromResult<int> and return the result");
        Assert.AreEqual(84, result);
    }

    [TestMethod]
    public void InvokeMethod_NonGenericTask_CompletesWithoutError()
    {
        var service = new TestSignalRService2();
        var methodInfo = typeof(TestSignalRService2).GetMethod("HandleTaskFromResultNoParams")!;
        
        var result = methodInfo.InvokeMethod(service);
    }

    #endregion

    #region GenericAttributes Tests

    [TestMethod]
    public async Task GenericAttributes_WithDateTimeString_PreservesExactValue()
    {
        var dto = new TestDtoWithGenericAttributes
        {
            Id = 123,
            Name = "Test Order",
            GenericAttributes =
            [
                new TestGenericAttribute { Id = 1, Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" },
                new TestGenericAttribute { Id = 2, Key = "Priority", Value = "1" },
                new TestGenericAttribute { Id = 3, Key = "SomeFlag", Value = "true" }
            ]
        };
        
        var result = await _client.PostDataAsync<TestDtoWithGenericAttributes, TestDtoWithGenericAttributes>(
            TestSignalRTags.GenericAttributesParam, dto);
        
        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual(123, result.Id);
        Assert.AreEqual("Test Order", result.Name);
        Assert.AreEqual(3, result.GenericAttributes.Count);
        
        var dateAttr = result.GenericAttributes.FirstOrDefault(x => x.Key == "DateOfReceipt");
        Assert.IsNotNull(dateAttr, "DateOfReceipt attribute should exist");
        Assert.AreEqual("10/24/2025 00:27:00", dateAttr.Value);
        
        var priorityAttr = result.GenericAttributes.FirstOrDefault(x => x.Key == "Priority");
        Assert.IsNotNull(priorityAttr);
        Assert.AreEqual("1", priorityAttr.Value);
        
        var flagAttr = result.GenericAttributes.FirstOrDefault(x => x.Key == "SomeFlag");
        Assert.IsNotNull(flagAttr);
        Assert.AreEqual("true", flagAttr.Value);
    }

    [TestMethod]
    public async Task GenericAttributes_EmptyList_PreservesEmptyList()
    {
        var dto = new TestDtoWithGenericAttributes
        {
            Id = 789,
            Name = "Empty Attributes",
            GenericAttributes = []
        };
        
        var result = await _client.PostDataAsync<TestDtoWithGenericAttributes, TestDtoWithGenericAttributes>(
            TestSignalRTags.GenericAttributesParam, dto);
        
        Assert.IsNotNull(result);
        Assert.AreEqual(789, result.Id);
        Assert.AreEqual("Empty Attributes", result.Name);
        Assert.IsNotNull(result.GenericAttributes);
        Assert.AreEqual(0, result.GenericAttributes.Count);
    }

    [TestMethod]
    public async Task GenericAttributes_ManyItems_AllPreserved()
    {
        var dto = new TestDtoWithGenericAttributes
        {
            Id = 999,
            Name = "Many Attributes",
            GenericAttributes = Enumerable.Range(0, 50).Select(i => new TestGenericAttribute
            {
                Id = i,
                Key = $"Attribute_{i}",
                Value = i % 5 == 0 ? $"{i}/24/2025 00:00:00" : i.ToString()
            }).ToList()
        };
        
        var result = await _client.PostDataAsync<TestDtoWithGenericAttributes, TestDtoWithGenericAttributes>(
            TestSignalRTags.GenericAttributesParam, dto);
        
        Assert.IsNotNull(result);
        Assert.AreEqual(50, result.GenericAttributes.Count);
        
        for (int i = 0; i < 50; i++)
        {
            var expectedValue = i % 5 == 0 ? $"{i}/24/2025 00:00:00" : i.ToString();
            Assert.AreEqual($"Attribute_{i}", result.GenericAttributes[i].Key);
            Assert.AreEqual(expectedValue, result.GenericAttributes[i].Value);
        }
    }

    #endregion

    #region StockTaking Production Bug Reproduction
    
    /// <summary>
    /// CRITICAL PRODUCTION BUG TEST: Reproduces the exact GetStockTakings(false) scenario.
    /// Bug: "Type mismatch for property 'Created' (index 3/5) on 'StockTaking'"
    /// 
    /// Root cause hypothesis: When IsClosed=false, the serializer skips it (default value optimization),
    /// but the first item has IsClosed=false while others have IsClosed=true.
    /// This may cause property index mismatch between server and client.
    /// Uses the REAL StockTaking model from StockTakingTestModels.cs
    /// </summary>
    [TestMethod]
    public async Task GetStockTakings_WithNullItems_RoundTrip()
    {
        // Simulate production: loadRelations = false
        var result = await _client.PostDataAsync<bool, List<StockTaking>>(
            TestSignalRTags.GetStockTakings, false);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual(5, result.Count, "Should have 5 StockTakings from production DB");

        // Verify first item (IsClosed = false - this is where the bug occurs!)
        var first = result[0];
        Assert.AreEqual(7, first.Id, "First Id should be 7");
        Assert.AreEqual(6, first.Creator, 
            $"First Creator should be 6, got {first.Creator}. " +
            "If this is a very large number, DateTime bytes were interpreted as int!");
        Assert.IsFalse(first.IsClosed, "First IsClosed should be false");
        Assert.IsNull(first.StockTakingItems, "StockTakingItems should be null when loadRelations=false");

        // Verify second item (IsClosed = true)
        var second = result[1];
        Assert.AreEqual(6, second.Id, "Second Id should be 6");
        Assert.AreEqual(6, second.Creator, "Second Creator should be 6");
        Assert.IsTrue(second.IsClosed, "Second IsClosed should be true");

        // Verify all items have correct Creator = 6
        foreach (var item in result)
        {
            Assert.AreEqual(6, item.Creator, 
                $"StockTaking Id={item.Id}: Creator should be 6, got {item.Creator}");
        }
    }

    /// <summary>
    /// Test with loadRelations = true (StockTakingItems is empty list, not null).
    /// </summary>
    [TestMethod]
    public async Task GetStockTakings_WithEmptyItems_RoundTrip()
    {
        var result = await _client.PostDataAsync<bool, List<StockTaking>>(
            TestSignalRTags.GetStockTakings, true);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual(5, result.Count, "Should have 5 StockTakings");

        foreach (var item in result)
        {
            Assert.AreEqual(6, item.Creator, 
                $"StockTaking Id={item.Id}: Creator should be 6, got {item.Creator}");
            Assert.IsNotNull(item.StockTakingItems, 
                $"StockTaking Id={item.Id}: StockTakingItems should not be null when loadRelations=true");
        }
    }

    #endregion

    #region Default Parameter Tests

    /// <summary>
    /// Tests calling a method with two parameters where the second has a default value,
    /// but only passing the first parameter.
    /// Expected: The default value (true) should be used for the optional parameter.
    /// </summary>
    [TestMethod]
    public async Task Post_TwoParamsWithDefault_CalledWithOneParam_UsesDefaultValue()
    {
        // Call with only one parameter - the second parameter should use its default value (true)
        var result = await _client.PostDataAsync<int, string>(TestSignalRTags.TwoParamsWithDefault, 42);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual("Required: 42, Optional: True", result, 
            "The optional parameter should use its default value (true)");
    }

    /// <summary>
    /// Tests calling a method with two parameters where the second has a default value,
    /// passing both parameters explicitly.
    /// Expected: The explicitly passed value (false) should override the default.
    /// </summary>
    [TestMethod]
    public async Task Post_TwoParamsWithDefault_CalledWithBothParams_UsesExplicitValue()
    {
        // Call with both parameters - explicitly set optional to false
        var result = await _client.PostAsync<string>(TestSignalRTags.TwoParamsWithDefault, [42, false]);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual("Required: 42, Optional: False", result, 
            "The optional parameter should use the explicitly passed value (false)");
    }

    /// <summary>
    /// Tests calling with both parameters set to true explicitly.
    /// </summary>
    [TestMethod]
    public async Task Post_TwoParamsWithDefault_CalledWithBothParamsTrue_UsesExplicitValue()
    {
        var result = await _client.PostAsync<string>(TestSignalRTags.TwoParamsWithDefault, [100, true]);

        Assert.IsNotNull(result, "Result should not be null");
        Assert.AreEqual("Required: 100, Optional: True", result);
    }

    #endregion

    #region Large Dataset Tests

    [TestMethod]
    public async Task RoundTrip_LargeOrderList_PreservesAllData()
    {
        TestDataFactory.ResetIdCounter();
        var dataSets = BenchmarkTestDataProvider.CreateTestDataSets(resetId: false);
        var orders = dataSets.Select(ds => ds.GetOrder<TestOrder_All_True>()).ToList();

        var result = await _client.PostDataAsync<List<TestOrder_All_True>, List<TestOrder_All_True>>(
            TestSignalRTags.TestOrderListParam, orders);

        Assert.IsNotNull(result);
        Assert.AreEqual(orders.Count, result.Count);
        for (int i = 0; i < orders.Count; i++)
        {
            Assert.AreEqual(orders[i].Id, result[i].Id);
            Assert.AreEqual(orders[i].OrderNumber, result[i].OrderNumber);
            Assert.AreEqual(orders[i].Items.Count, result[i].Items.Count);
        }
    }

    /// <summary>
    /// 250KB+ payload with ASCII-only strings.
    /// Validates BWO owned-buffer path with many Grow cycles.
    /// </summary>
    [TestMethod]
    public async Task RoundTrip_VeryLargeOrderList_250KB_PreservesAllData()
    {
        TestDataFactory.ResetIdCounter();
        var orders = new List<TestOrder_All_True>();
        for (var i = 0; i < 100; i++)
        {
            var tag = TestDataFactory.CreateTag($"Tag_{i}");
            var user = TestDataFactory.CreateUser($"user_{i}");
            orders.Add(TestDataFactory.CreateOrder(
                itemCount: 5,
                palletsPerItem: 3,
                measurementsPerPallet: 3,
                pointsPerMeasurement: 5,
                sharedTag: tag,
                sharedUser: user));
        }

        var result = await _client.PostDataAsync<List<TestOrder_All_True>, List<TestOrder_All_True>>(
            TestSignalRTags.TestOrderListParam, orders);

        Assert.IsNotNull(result, "Deserialization returned null — likely BWO GetTotalPosition drift on large payload");
        Assert.AreEqual(orders.Count, result.Count, "Order count mismatch");
        for (var i = 0; i < orders.Count; i++)
        {
            Assert.AreEqual(orders[i].Id, result[i].Id, $"Order[{i}].Id mismatch");
            Assert.AreEqual(orders[i].Items.Count, result[i].Items.Count, $"Order[{i}].Items.Count mismatch");
        }
    }

    /// <summary>
    /// Large payload with non-ASCII (Hungarian) strings — targets the WriteStringUtf8
    /// savedPosition bug that causes BWO GetTotalPosition drift when a non-ASCII string
    /// straddles a chunk boundary and Grow resets _position.
    ///
    /// Production evidence: Array=44111, BWO_pipe=56340, BWO_temp=44111
    /// The bug only manifests when TryGetArray fails (MemoryManager-backed Memory)
    /// AND non-ASCII strings trigger the UTF-8 fallback with savedPosition rewind.
    /// </summary>
    [TestMethod]
    public async Task RoundTrip_LargeOrderList_NonAsciiStrings_PreservesAllData()
    {
        // Non-ASCII strings that trigger UTF-8 fallback in WriteStringUtf8
        var hungarianNames = new[]
        {
            "Kürtőskalács Értékesítő Kft.",
            "Széchenyi István Tér",
            "Büfé és Étkezde Zrt.",
            "Különleges Árú Raktár",
            "Möbius Szállítmányozás",
            "Tündérkert Óvoda és Bölcsőde",
            "Győri Főpályaudvar",
            "Hősök Tere Múzeum",
            "Péterfy Sándor Utcai Kórház",
            "Közgondnokság Ügyfélfogadó"
        };

        TestDataFactory.ResetIdCounter();
        var orders = new List<TestOrder_All_True>();
        for (var i = 0; i < 80; i++)
        {
            var tag = TestDataFactory.CreateTag($"Cég_{i}_{hungarianNames[i % hungarianNames.Length]}");
            var user = TestDataFactory.CreateUser($"felhasználó_{i}");
            user.FirstName = hungarianNames[i % hungarianNames.Length];
            user.LastName = hungarianNames[(i + 3) % hungarianNames.Length];
            user.Email = $"felhasználó_{i}@székesfehérvár.hu";
            user.Username = $"ügyfél_{hungarianNames[(i + 5) % hungarianNames.Length]}";

            var order = TestDataFactory.CreateOrder(
                itemCount: 4,
                palletsPerItem: 3,
                measurementsPerPallet: 3,
                pointsPerMeasurement: 4,
                sharedTag: tag,
                sharedUser: user);

            // Inject non-ASCII product names to maximize chunk-boundary collisions
            foreach (var item in order.Items)
            {
                item.ProductName = $"Termék_{hungarianNames[(i + item.Id) % hungarianNames.Length]}_{i}";
            }

            order.OrderNumber = $"RND-{i:D4}-{hungarianNames[i % hungarianNames.Length]}";
            orders.Add(order);
        }

        var result = await _client.PostDataAsync<List<TestOrder_All_True>, List<TestOrder_All_True>>(
            TestSignalRTags.TestOrderListParam, orders);

        Assert.IsNotNull(result,
            "Deserialization returned null — BWO savedPosition drift on non-ASCII strings at chunk boundary");
        Assert.AreEqual(orders.Count, result.Count, "Order count mismatch");
        for (var i = 0; i < orders.Count; i++)
        {
            Assert.AreEqual(orders[i].Id, result[i].Id, $"Order[{i}].Id mismatch");
            Assert.AreEqual(orders[i].OrderNumber, result[i].OrderNumber, $"Order[{i}].OrderNumber mismatch");
            Assert.AreEqual(orders[i].Items.Count, result[i].Items.Count, $"Order[{i}].Items.Count mismatch");
            for (var j = 0; j < orders[i].Items.Count; j++)
            {
                Assert.AreEqual(orders[i].Items[j].ProductName, result[i].Items[j].ProductName,
                    $"Order[{i}].Items[{j}].ProductName mismatch — non-ASCII string corrupted");
            }
        }
    }

    /// <summary>
    /// Sanity check: SlabTransportWriter.GetMemory returns MemoryManager-backed Memory
    /// so TryGetArray fails, forcing BWO into the owned-buffer code path.
    /// </summary>
    [TestMethod]
    public void SlabTransportWriter_GetMemory_TryGetArrayFails()
    {
        SlabTransportWriter.VerifyMemoryManagerBacked();
    }

    #endregion
}

/// <summary>
/// Runs all SignalR tests with JSON serialization.
/// </summary>

[TestClass]
public class SignalRClientToHubTest_Json : SignalRClientToHubTestBase
{
    protected override AcSerializerOptions SerializerOption { get; } = new AcJsonSerializerOptions();
}

/// <summary>
/// Runs all SignalR tests with Binary serialization.
/// </summary>
[TestClass]
public class SignalRClientToHubTest_Binary_WithRef : SignalRClientToHubTestBase
{
    protected override AcSerializerOptions SerializerOption { get; } = new AcBinarySerializerOptions();
}

/// <summary>
/// Runs all SignalR tests with Binary serialization.
/// </summary>
[TestClass]
public class SignalRClientToHubTest_Binary_NoRef : SignalRClientToHubTestBase
{
    protected override AcSerializerOptions SerializerOption { get; } = new AcBinarySerializerOptions
    {
        ReferenceHandling = ReferenceHandlingMode.None
    };
}