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

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for navigation property serialization issues.
/// 
/// CRITICAL BUG REPRODUCTION:
/// When a navigation property (like StockTakingItem.Product) is populated,
/// the serializer writes properties of the navigation target (Product),
/// but these property names were NOT registered in the metadata header!
/// 
/// The bug pattern:
/// 1. RegisterMetadataForType walks List&lt;StockTakingItem&gt; and registers StockTakingItem properties
/// 2. StockTakingItem has a "Product" property of type Product - this property NAME is registered
/// 3. BUT Product's own properties (Name, Description, Price, CategoryId) are NOT registered!
/// 4. When Product is NOT NULL at runtime, WriteObject writes Product's property indices
/// 5. GetPropertyNameIndex returns NEW indices that weren't in the header!
/// 6. Deserializer reads property indices that don't exist in its table ? crash/type mismatch
/// </summary>
[TestClass]
public class AcBinarySerializerNavigationPropertyTests
{
    /// <summary>
    /// CRITICAL REGRESSION TEST: Navigation properties causing metadata mismatch.
    /// This is the EXACT production scenario:
    /// - StockTakingItem.Product is populated by the database query
    /// - Product's properties are serialized with wrong indices
    /// - Deserializer fails with type mismatch
    /// </summary>
    [TestMethod]
    public void Deserialize_NavigationPropertyPopulated_MetadataIncludesNestedType()
    {
        var parent = new ParentWithNavigatingItems
        {
            Id = 1,
            Name = "Parent",
            Creator = 6, // The exact value from production error
            Created = new DateTime(2025, 1, 24, 15, 25, 0, DateTimeKind.Utc),
            Modified = DateTime.UtcNow,
            Items = new List<ItemWithNavigationProperty>
            {
                new()
                {
                    Id = 10,
                    ParentId = 1,
                    ProductId = 100,
                    IsMeasured = true,
                    Quantity = 50,
                    Created = DateTime.UtcNow.AddHours(-1),
                    Modified = DateTime.UtcNow,
                    // Navigation property IS populated - this is the key!
                    Product = new ProductEntity
                    {
                        Id = 100,
                        Name = "TestProduct",
                        Description = "Product description with long text",
                        Price = 99.99,
                        CategoryId = 5,
                        Created = DateTime.UtcNow.AddDays(-30)
                    }
                }
            }
        };

        var binary = parent.ToBinary();
        var result = binary.BinaryTo<ParentWithNavigatingItems>();

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual(6, result.Creator, "Creator should be 6");
        Assert.AreEqual(parent.Created.Ticks, result.Created.Ticks,
            $"Created mismatch. Expected: {parent.Created}, Got: {result.Created}");

        Assert.IsNotNull(result.Items);
        Assert.AreEqual(1, result.Items.Count);

        var item = result.Items[0];
        Assert.AreEqual(10, item.Id);
        Assert.AreEqual(100, item.ProductId);
        
        // Navigation property should be deserialized correctly
        Assert.IsNotNull(item.Product, "Product navigation property should not be null");
        Assert.AreEqual(100, item.Product.Id);
        Assert.AreEqual("TestProduct", item.Product.Name);
        Assert.AreEqual(5, item.Product.CategoryId);
    }

    /// <summary>
    /// Test with multiple items, some with Product populated, some without.
    /// This creates a mixed scenario where some items have navigation properties.
    /// </summary>
    [TestMethod]
    public void Deserialize_MixedNavigationProperties_AllItemsCorrect()
    {
        var parent = new ParentWithNavigatingItems
        {
            Id = 1,
            Name = "Parent",
            Creator = 6,
            Created = DateTime.UtcNow,
            Modified = DateTime.UtcNow,
            Items = Enumerable.Range(1, 5).Select(i => new ItemWithNavigationProperty
            {
                Id = i * 10,
                ParentId = 1,
                ProductId = 100 + i,
                IsMeasured = i % 2 == 0,
                Quantity = i * 10,
                Created = DateTime.UtcNow.AddHours(-i),
                Modified = DateTime.UtcNow,
                // Only populate Product for even items
                Product = i % 2 == 0 ? new ProductEntity
                {
                    Id = 100 + i,
                    Name = $"Product_{i}",
                    Description = $"Description for product {i}",
                    Price = i * 10.5,
                    CategoryId = i % 3,
                    Created = DateTime.UtcNow.AddDays(-i)
                } : null
            }).ToList()
        };

        var binary = parent.ToBinary();
        var result = binary.BinaryTo<ParentWithNavigatingItems>();

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual(6, result.Creator);

        Assert.IsNotNull(result.Items);
        Assert.AreEqual(5, result.Items.Count);

        for (int i = 1; i <= 5; i++)
        {
            var item = result.Items[i - 1];
            Assert.AreEqual(i * 10, item.Id, $"Item {i} Id mismatch");
            
            if (i % 2 == 0)
            {
                Assert.IsNotNull(item.Product, $"Item {i} should have Product");
                Assert.AreEqual($"Product_{i}", item.Product.Name);
            }
            else
            {
                Assert.IsNull(item.Product, $"Item {i} should not have Product");
            }
        }
    }

    /// <summary>
    /// Test with list of parents, each with items with navigation properties.
    /// This is the exact production scenario - multiple StockTaking entities
    /// each with StockTakingItems that have Product navigation properties.
    /// </summary>
    [TestMethod]
    public void Deserialize_ListOfParentsWithNavigationProperties_AllCorrect()
    {
        var parents = Enumerable.Range(1, 3).Select(p => new ParentWithNavigatingItems
        {
            Id = p,
            Name = $"Parent_{p}",
            Creator = p,
            Created = DateTime.UtcNow.AddDays(-p),
            Modified = DateTime.UtcNow,
            Items = Enumerable.Range(1, 2).Select(i => new ItemWithNavigationProperty
            {
                Id = p * 100 + i,
                ParentId = p,
                ProductId = 1000 + i,
                IsMeasured = true,
                Quantity = 10 * i,
                Created = DateTime.UtcNow.AddHours(-i),
                Modified = DateTime.UtcNow,
                Product = new ProductEntity
                {
                    Id = 1000 + i,
                    Name = $"Product_{p}_{i}",
                    Description = $"Description {p}_{i}",
                    Price = (p * 10) + (i * 1.5),
                    CategoryId = i % 3,
                    Created = DateTime.UtcNow.AddDays(-10)
                }
            }).ToList()
        }).ToList();

        var binary = parents.ToBinary();
        var result = binary.BinaryTo<List<ParentWithNavigatingItems>>();

        Assert.IsNotNull(result);
        Assert.AreEqual(3, result.Count);

        for (int p = 0; p < 3; p++)
        {
            var parent = result[p];
            Assert.AreEqual(p + 1, parent.Id, $"Parent[{p}].Id mismatch");
            Assert.AreEqual(p + 1, parent.Creator, $"Parent[{p}].Creator mismatch");

            Assert.IsNotNull(parent.Items);
            Assert.AreEqual(2, parent.Items.Count);

            for (int i = 0; i < 2; i++)
            {
                var item = parent.Items[i];
                Assert.IsNotNull(item.Product, $"Parent[{p}].Items[{i}].Product should not be null");
                Assert.AreEqual($"Product_{p + 1}_{i + 1}", item.Product.Name);
            }
        }
    }

    /// <summary>
    /// Test deeply nested navigation properties.
    /// Product has a Category, Category has a Parent, etc.
    /// </summary>
    [TestMethod]
    public void Deserialize_DeeplyNestedNavigationProperties_AllCorrect()
    {
        // This tests that the serializer correctly handles navigation properties
        // even when they are deeply nested (Product -> Category -> Parent)
        var parent = new ParentWithNavigatingItems
        {
            Id = 1,
            Name = "Parent",
            Creator = 6,
            Created = DateTime.UtcNow,
            Modified = DateTime.UtcNow,
            Items = new List<ItemWithNavigationProperty>
            {
                new()
                {
                    Id = 10,
                    ParentId = 1,
                    ProductId = 100,
                    IsMeasured = true,
                    Quantity = 50,
                    Created = DateTime.UtcNow,
                    Modified = DateTime.UtcNow,
                    Product = new ProductEntity
                    {
                        Id = 100,
                        Name = "ProductWithDetails",
                        Description = "Very long description that should be interned",
                        Price = 123.45,
                        CategoryId = 10,
                        Created = DateTime.UtcNow.AddMonths(-6)
                    }
                },
                new()
                {
                    Id = 20,
                    ParentId = 1,
                    ProductId = 200,
                    IsMeasured = false,
                    Quantity = 25,
                    Created = DateTime.UtcNow,
                    Modified = DateTime.UtcNow,
                    Product = new ProductEntity
                    {
                        Id = 200,
                        Name = "AnotherProduct",
                        Description = "Another description",
                        Price = 67.89,
                        CategoryId = 20,
                        Created = DateTime.UtcNow.AddMonths(-3)
                    }
                }
            }
        };

        var binary = parent.ToBinary();
        
        // Log binary size for debugging
        Console.WriteLine($"Binary size: {binary.Length} bytes");
        
        var result = binary.BinaryTo<ParentWithNavigatingItems>();

        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual(6, result.Creator);
        
        Assert.IsNotNull(result.Items);
        Assert.AreEqual(2, result.Items.Count);
        
        // First item
        Assert.AreEqual(10, result.Items[0].Id);
        Assert.IsNotNull(result.Items[0].Product);
        Assert.AreEqual("ProductWithDetails", result.Items[0].Product.Name);
        Assert.AreEqual(123.45, result.Items[0].Product.Price);
        
        // Second item
        Assert.AreEqual(20, result.Items[1].Id);
        Assert.IsNotNull(result.Items[1].Product);
        Assert.AreEqual("AnotherProduct", result.Items[1].Product.Name);
        Assert.AreEqual(67.89, result.Items[1].Product.Price);
    }

    /// <summary>
    /// Test with same Product instance referenced multiple times.
    /// This tests the reference handling with navigation properties.
    /// </summary>
    [TestMethod]
    public void Deserialize_SharedNavigationProperty_ReferencesPreserved()
    {
        // Create a shared Product that is referenced by multiple items
        var sharedProduct = new ProductEntity
        {
            Id = 999,
            Name = "SharedProduct",
            Description = "This product is shared across items",
            Price = 50.00,
            CategoryId = 1,
            Created = DateTime.UtcNow.AddYears(-1)
        };

        var parent = new ParentWithNavigatingItems
        {
            Id = 1,
            Name = "Parent",
            Creator = 6,
            Created = DateTime.UtcNow,
            Modified = DateTime.UtcNow,
            Items = new List<ItemWithNavigationProperty>
            {
                new()
                {
                    Id = 10,
                    ParentId = 1,
                    ProductId = 999,
                    IsMeasured = true,
                    Quantity = 50,
                    Created = DateTime.UtcNow,
                    Modified = DateTime.UtcNow,
                    Product = sharedProduct // Same reference
                },
                new()
                {
                    Id = 20,
                    ParentId = 1,
                    ProductId = 999,
                    IsMeasured = false,
                    Quantity = 75,
                    Created = DateTime.UtcNow,
                    Modified = DateTime.UtcNow,
                    Product = sharedProduct // Same reference
                }
            }
        };

        var binary = parent.ToBinary();
        var result = binary.BinaryTo<ParentWithNavigatingItems>();

        Assert.IsNotNull(result);
        Assert.IsNotNull(result.Items);
        Assert.AreEqual(2, result.Items.Count);
        
        // Both items should have the same Product values
        Assert.IsNotNull(result.Items[0].Product);
        Assert.IsNotNull(result.Items[1].Product);
        Assert.AreEqual(999, result.Items[0].Product.Id);
        Assert.AreEqual(999, result.Items[1].Product.Id);
        Assert.AreEqual("SharedProduct", result.Items[0].Product.Name);
        Assert.AreEqual("SharedProduct", result.Items[1].Product.Name);
        
        // With reference handling, they should be the same instance
        // (This depends on ReferenceHandling being enabled)
        Console.WriteLine($"Same instance: {ReferenceEquals(result.Items[0].Product, result.Items[1].Product)}");
    }
}
