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

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for circular reference handling with back-navigation properties.
/// </summary>
[TestClass]
public class AcBinarySerializerCircularReferenceTests
{
    /// <summary>
    /// CRITICAL TEST: Circular references with back-navigation properties.
    /// This simulates the exact production scenario where:
    /// - StockTaking has StockTakingItems collection
    /// - StockTakingItem has StockTaking back-reference (circular!)
    /// - StockTakingItem has Product navigation property
    /// </summary>
    [TestMethod]
    [DataRow(true, true)]
    [DataRow(false, true)]
    [DataRow(true, false)]
    [DataRow(false, false)]
    public void Deserialize_CircularReference_ParentChildBackReference(bool useSgen, bool useMeta)
    {
        var parent = new CircularParent
        {
            Id = 1,
            Name = "Parent",
            Created = new DateTime(2025, 1, 24, 15, 25, 0, DateTimeKind.Utc),
            Modified = DateTime.UtcNow,
            Creator = 6,
            Children = new List<CircularChild>()
        };

        var child = new CircularChild
        {
            Id = 10,
            ParentId = 1,
            Name = "Child",
            Created = DateTime.UtcNow.AddHours(-1),
            Modified = DateTime.UtcNow,
            Parent = parent,
            GrandChildren = new List<CircularGrandChild>()
        };

        var grandChild = new CircularGrandChild
        {
            Id = 100,
            ChildId = 10,
            CreatorId = 6,
            ModifierId = null,
            Created = DateTime.UtcNow,
            Modified = DateTime.UtcNow,
            Child = child
        };

        child.GrandChildren.Add(grandChild);
        parent.Children.Add(child);

        var option = AcBinarySerializerOptions.Default;
        option.ReferenceHandling = ReferenceHandlingMode.All;
        option.UseGeneratedCode = useSgen;
        option.UseMetadata = useMeta;

        Console.WriteLine($"\n========== ReferenceHandling: {option.ReferenceHandling}, UseSgen: {option.UseGeneratedCode}, UseMeta: {option.UseMetadata} ==========");

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

        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.Children);
        Assert.AreEqual(1, result.Children.Count);
        
        var resultChild = result.Children[0];
        Assert.AreEqual(10, resultChild.Id);
        Assert.AreEqual(resultChild.Created.Ticks, child.Created.Ticks, "Child.Created should match");
        
        Assert.IsNotNull(resultChild.Parent, "Child.Parent back-reference should be resolved");
        Assert.AreEqual(1, resultChild.Parent.Id, "Back-reference should point to same parent");
    }

    /// <summary>
    /// Test list of parents with circular references.
    /// </summary>
    [TestMethod]
    [DataRow(true, true)]
    [DataRow(false, true)]
    [DataRow(true, false)]
    [DataRow(false, false)]
    public void Deserialize_ListOfCircularReferences_AllItemsCorrect(bool useSgen, bool useMeta)
    {
        var parents = Enumerable.Range(1, 5).Select(p =>
        {
            var parent = new CircularParent
            {
                Id = p,
                Name = $"Parent_{p}",
                Created = DateTime.UtcNow.AddDays(-p),
                Modified = DateTime.UtcNow,
                Creator = p,
                Children = new List<CircularChild>()
            };

            for (int c = 1; c <= 2; c++)
            {
                var child = new CircularChild
                {
                    Id = p * 100 + c,
                    ParentId = p,
                    Name = $"Child_{p}_{c}",
                    Created = DateTime.UtcNow.AddHours(-c),
                    Modified = DateTime.UtcNow,
                    Parent = parent,
                    GrandChildren = new List<CircularGrandChild>()
                };

                for (int g = 1; g <= 2; g++)
                {
                    child.GrandChildren.Add(new CircularGrandChild
                    {
                        Id = p * 1000 + c * 100 + g,
                        ChildId = child.Id,
                        CreatorId = g % 2 == 0 ? p : null,
                        ModifierId = g % 2 == 1 ? p * 2 : null,
                        Created = DateTime.UtcNow,
                        Modified = DateTime.UtcNow,
                        Child = child
                    });
                }

                parent.Children.Add(child);
            }

            return parent;
        }).ToList();

        var option = AcBinarySerializerOptions.Default;
        option.ReferenceHandling = ReferenceHandlingMode.All;
        option.UseGeneratedCode = useSgen;
        option.UseMetadata = useMeta;

        Console.WriteLine($"\n========== ReferenceHandling: {option.ReferenceHandling}, UseSgen: {option.UseGeneratedCode}, UseMeta: {option.UseMetadata} ==========");

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

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

        for (int p = 0; p < 5; p++)
        {
            var original = parents[p];
            var deserialized = result[p];

            Assert.AreEqual(original.Id, deserialized.Id, $"Parent[{p}].Id mismatch");
            Assert.AreEqual(original.Creator, deserialized.Creator, $"Parent[{p}].Creator mismatch");
            Assert.AreEqual(original.Created.Ticks, deserialized.Created.Ticks,
                $"Parent[{p}].Created mismatch. Expected: {original.Created}, Got: {deserialized.Created}");

            Assert.IsNotNull(deserialized.Children, $"Parent[{p}].Children is null");
            Assert.AreEqual(2, deserialized.Children.Count, $"Parent[{p}].Children.Count mismatch");

            for (int c = 0; c < 2; c++)
            {
                var origChild = original.Children![c];
                var deserChild = deserialized.Children[c];

                Assert.AreEqual(origChild.Id, deserChild.Id, $"Parent[{p}].Children[{c}].Id mismatch");
                Assert.AreEqual(origChild.Created.Ticks, deserChild.Created.Ticks,
                    $"Parent[{p}].Children[{c}].Created mismatch");
                
                Assert.IsNotNull(deserChild.Parent, $"Parent[{p}].Children[{c}].Parent should not be null");
            }
        }
    }
}
