using AyCode.Core.Extensions;
using AyCode.Core.Tests.TestModels;

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for Chain API reference preservation with IId objects.
/// This is the critical feature for DevExpress DXGrid GridCustomDataSource scenario.
/// </summary>
[TestClass]
public class AcBinarySerializerChainReferenceTests
{
    /// <summary>
    /// CRITICAL TEST: DevExpress DXGrid scenario with Chain API.
    /// Server returns List&lt;Item&gt; for grid display, but we also have internal cache List&lt;Item&gt;.
    /// When using ThenPopulate, the grid's visible items MUST be the same object references
    /// from the cache to ensure Blazor binding works correctly.
    /// </summary>
    [TestMethod]
    public void ChainPopulate_IIdObjects_PreservesReferences()
    {
        // Setup: Create internal cache with 5 categories
        var internalCache = new List<SharedCategory_All_True>
        {
            new() { Id = 1, Name = "Category1", SortOrder = 1 },
            new() { Id = 2, Name = "Category2", SortOrder = 2 },
            new() { Id = 3, Name = "Category3", SortOrder = 3 },
            new() { Id = 4, Name = "Category4", SortOrder = 4 },
            new() { Id = 5, Name = "Category5", SortOrder = 5 }
        };

        // Server returns subset of categories (like grid pagination - page 2: items 3-5)
        var serverData = new List<SharedCategory_All_True>
        {
            new() { Id = 3, Name = "Category3_Updated", SortOrder = 33 },
            new() { Id = 4, Name = "Category4_Updated", SortOrder = 44 },
            new() { Id = 5, Name = "Category5_Updated", SortOrder = 55 }
        };

        // Serialize server response
        var binary = serverData.ToBinary();

        // Grid's visible list (empty initially)
        var gridVisibleList = new List<SharedCategory_All_True>();

        // CRITICAL: Use Chain API to parse once, populate both cache and grid
        using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();

        // First: Update internal cache (will become 3 items: 3-5 updated)
        chain.ThenPopulate(internalCache);

        // Second: Populate grid's visible list
        chain.ThenPopulate(gridVisibleList);

        // VERIFICATION: After ThenPopulate, internalCache contains the 3 items from server
        Assert.AreEqual(3, gridVisibleList.Count);
        Assert.AreEqual(3, internalCache.Count, "ThenPopulate replaces list contents with server data");

        // CRITICAL ASSERTION: Grid items MUST be same object references as cache items!
        Assert.AreSame(internalCache[0], gridVisibleList[0],
            "Grid item MUST be same reference as cache item for Blazor binding!");
        Assert.AreSame(internalCache[1], gridVisibleList[1],
            "Grid item MUST be same reference as cache item for Blazor binding!");
        Assert.AreSame(internalCache[2], gridVisibleList[2],
            "Grid item MUST be same reference as cache item for Blazor binding!");

        // Verify data was updated correctly
        Assert.AreEqual(3, internalCache[0].Id);
        Assert.AreEqual("Category3_Updated", internalCache[0].Name);
        Assert.AreEqual(33, internalCache[0].SortOrder);
    }

    /// <summary>
    /// Test JSON Chain API reference preservation.
    /// </summary>
    [TestMethod]
    public void JsonChainPopulate_IIdObjects_PreservesReferences()
    {
        // Setup: Create internal cache
        var internalCache = new List<SharedCategory_All_True>
        {
            new() { Id = 1, Name = "Category1", SortOrder = 1 },
            new() { Id = 2, Name = "Category2", SortOrder = 2 },
            new() { Id = 3, Name = "Category3", SortOrder = 3 }
        };

        // Server returns subset
        var serverData = new List<SharedCategory_All_True>
        {
            new() { Id = 2, Name = "Category2_Updated", SortOrder = 22 },
            new() { Id = 3, Name = "Category3_Updated", SortOrder = 33 }
        };

        // Serialize server response
        var json = serverData.ToJson();

        // Grid's visible list
        var gridVisibleList = new List<SharedCategory_All_True>();

        // Use JSON Chain API
        using var chain = json.JsonToChain<List<SharedCategory_All_True>>();

        // Update internal cache (will replace with 2 items)
        chain.ThenPopulate(internalCache);

        // Populate grid's visible list
        chain.ThenPopulate(gridVisibleList);

        // VERIFICATION
        Assert.AreEqual(2, gridVisibleList.Count);
        Assert.AreEqual(2, internalCache.Count, "ThenPopulate replaces list contents");

        // CRITICAL: Same references!
        Assert.AreSame(internalCache[0], gridVisibleList[0]);
        Assert.AreSame(internalCache[1], gridVisibleList[1]);

        // Verify updates
        Assert.AreEqual(2, internalCache[0].Id);
        Assert.AreEqual("Category2_Updated", internalCache[0].Name);
        Assert.AreEqual(22, internalCache[0].SortOrder);
    }

    /// <summary>
    /// Test with Guid-based IId implementation.
    /// </summary>
    [TestMethod]
    public void ChainPopulate_GuidIId_PreservesReferences()
    {
        var cache = new List<TestGuidOrder>
        {
            new() { Id = Guid.NewGuid(), Code = "ORD-001", Count = 10 },
            new() { Id = Guid.NewGuid(), Code = "ORD-002", Count = 20 }
        };

        var id1 = cache[0].Id;
        var id2 = cache[1].Id;

        var serverData = new List<TestGuidOrder>
        {
            new() { Id = id1, Code = "ORD-001-UPDATED", Count = 11 },
            new() { Id = id2, Code = "ORD-002-UPDATED", Count = 22 }
        };

        var binary = serverData.ToBinary();
        var gridList = new List<TestGuidOrder>();

        using var chain = binary.BinaryToChain<List<TestGuidOrder>>();

        chain.ThenPopulate(cache);
        chain.ThenPopulate(gridList);

        Assert.AreEqual(2, gridList.Count);
        Assert.AreSame(cache[0], gridList[0], "Guid-based IId should also preserve references");
        Assert.AreSame(cache[1], gridList[1]);
        Assert.AreEqual("ORD-001-UPDATED", cache[0].Code);
    }

    /// <summary>
    /// Test multiple chain operations with different subsets.
    /// </summary>
    [TestMethod]
    public void ChainPopulate_MultipleSubsets_PreservesReferencesAcrossAll()
    {
        // Large internal cache
        var internalCache = Enumerable.Range(1, 10)
            .Select(i => new SharedCategory_All_True { Id = i, Name = $"Category{i}", SortOrder = i * 10 })
            .ToList();

        // Server returns items 3-7
        var serverData = Enumerable.Range(3, 5)
            .Select(i => new SharedCategory_All_True { Id = i, Name = $"Category{i}_Updated", SortOrder = i * 11 })
            .ToList();

        var binary = serverData.ToBinary();

        // Three different grid pages/views
        var gridPage1 = new List<SharedCategory_All_True>();
        var gridPage2 = new List<SharedCategory_All_True>();
        var gridPage3 = new List<SharedCategory_All_True>();

        using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();

        // Update cache first
        chain.ThenPopulate(internalCache);

        // Populate different grid pages
        chain.ThenPopulate(gridPage1);
        chain.ThenPopulate(gridPage2);
        chain.ThenPopulate(gridPage3);

        // All pages should have same references
        Assert.AreEqual(5, gridPage1.Count);
        Assert.AreEqual(5, gridPage2.Count);
        Assert.AreEqual(5, gridPage3.Count);

        // All three pages point to the SAME objects
        for (int i = 0; i < 5; i++)
        {
            Assert.AreSame(gridPage1[i], gridPage2[i], $"Page1 and Page2 item {i} must be same reference");
            Assert.AreSame(gridPage2[i], gridPage3[i], $"Page2 and Page3 item {i} must be same reference");
            Assert.AreSame(internalCache[i], gridPage1[i], $"Cache and Page1 item {i} must be same reference");
        }
    }

    /// <summary>
    /// Simple debug test to verify chain reference tracking works.
    /// </summary>
    [TestMethod]
    public void ChainPopulate_SimpleCase_Works()
    {
        var list1 = new List<SharedCategory_All_True>();
        var list2 = new List<SharedCategory_All_True>();

        var serverData = new List<SharedCategory_All_True>
        {
            new() { Id = 1, Name = "Cat1", SortOrder = 10 }
        };

        var binary = serverData.ToBinary();

        using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();

        // First populate
        chain.ThenPopulate(list1);
        Assert.AreEqual(1, list1.Count);
        Assert.AreEqual(1, list1[0].Id);

        // Second populate - should reuse same object
        chain.ThenPopulate(list2);
        Assert.AreEqual(1, list2.Count);
        Assert.AreSame(list1[0], list2[0], "Should be same object reference!");
    }
}
