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

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for generic type parameter handling in serialization.
/// </summary>
[TestClass]
public class AcBinarySerializerGenericTypeTests
{
    /// <summary>
    /// CRITICAL REGRESSION TEST: Generic type parameter causing metadata mismatch.
    /// 
    /// The bug pattern:
    /// 1. Parent class uses generic type parameter: GenericParent&lt;TItem&gt; where TItem : IGenericItem
    /// 2. RegisterMetadataForType uses GetCollectionElementType which returns TItem (the interface/constraint)
    /// 3. But serialization uses runtime type (GenericItemImpl) which has MORE properties
    /// 4. Property indices in metadata table don't match what's being serialized
    /// 5. Deserialization reads wrong property indices ? type mismatch!
    /// </summary>
    [TestMethod]
    public void Deserialize_GenericTypeParameter_RuntimeTypeHasMoreProperties()
    {
        var parent = new ConcreteParent
        {
            Id = 1,
            Name = "Parent",
            Creator = 6,
            Created = new DateTime(2025, 1, 24, 15, 25, 0, DateTimeKind.Utc),
            Modified = DateTime.UtcNow,
            Items = new List<GenericItemImpl>
            {
                new()
                {
                    Id = 10,
                    Name = "Item1",
                    ExtraInt = 100,
                    Created = DateTime.UtcNow.AddHours(-1),
                    Modified = DateTime.UtcNow,
                    Description = "Description1"
                },
                new()
                {
                    Id = 20,
                    Name = "Item2",
                    ExtraInt = 200,
                    Created = DateTime.UtcNow.AddHours(-2),
                    Modified = DateTime.UtcNow,
                    Description = "Description2"
                }
            }
        };

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

        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(2, result.Items.Count);

        Assert.AreEqual(10, result.Items[0].Id);
        Assert.AreEqual(100, result.Items[0].ExtraInt, "ExtraInt should be preserved");
        Assert.AreEqual(parent.Items[0].Created.Ticks, result.Items[0].Created.Ticks,
            "Item Created should match");
    }

    /// <summary>
    /// Test with list of generic parents.
    /// </summary>
    [TestMethod]
    public void Deserialize_ListOfGenericParents_AllItemsCorrect()
    {
        var parents = Enumerable.Range(1, 5).Select(p => new ConcreteParent
        {
            Id = p,
            Name = $"Parent_{p}",
            Creator = p,
            Created = DateTime.UtcNow.AddDays(-p),
            Modified = DateTime.UtcNow,
            Items = Enumerable.Range(1, 3).Select(i => new GenericItemImpl
            {
                Id = p * 100 + i,
                Name = $"Item_{p}_{i}",
                ExtraInt = p * 10 + i,
                Created = DateTime.UtcNow.AddHours(-i),
                Modified = DateTime.UtcNow,
                Description = $"Desc_{p}_{i}"
            }).ToList()
        }).ToList();

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

        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.Items, $"Parent[{p}].Items is null");
            Assert.AreEqual(3, deserialized.Items.Count);

            for (int i = 0; i < 3; i++)
            {
                var origItem = original.Items![i];
                var deserItem = deserialized.Items[i];

                Assert.AreEqual(origItem.Id, deserItem.Id, $"Parent[{p}].Items[{i}].Id mismatch");
                Assert.AreEqual(origItem.ExtraInt, deserItem.ExtraInt, 
                    $"Parent[{p}].Items[{i}].ExtraInt mismatch");
                Assert.AreEqual(origItem.Created.Ticks, deserItem.Created.Ticks,
                    $"Parent[{p}].Items[{i}].Created mismatch");
            }
        }
    }

    /// <summary>
    /// Diagnostic: Check what PropertyType the reflection returns for generic type parameter.
    /// </summary>
    [TestMethod]
    public void Diagnostic_GenericProperty_ReflectionType()
    {
        var parentType = typeof(ConcreteParent);
        var itemsProp = parentType.GetProperty("Items");
        
        Assert.IsNotNull(itemsProp);
        
        var propType = itemsProp.PropertyType;
        Console.WriteLine($"PropertyType: {propType}");
        Console.WriteLine($"PropertyType.FullName: {propType.FullName}");
        Console.WriteLine($"IsGenericType: {propType.IsGenericType}");
        
        if (propType.IsGenericType)
        {
            var args = propType.GetGenericArguments();
            Console.WriteLine($"GenericArguments.Length: {args.Length}");
            foreach (var arg in args)
            {
                Console.WriteLine($"  GenericArgument: {arg.FullName}");
            }
        }
        
        Assert.IsTrue(propType.IsGenericType);
        var elementType = propType.GetGenericArguments()[0];
        Assert.AreEqual(typeof(GenericItemImpl), elementType, 
            "Element type should be GenericItemImpl, not IGenericItem");
    }
}
