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

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for object serialization including nested objects, lists, and dictionaries.
/// </summary>
[TestClass]
public class AcBinarySerializerObjectTests
{
    [TestMethod]
    public void Serialize_SimpleObject_RoundTrip()
    {
        var obj = new TestSimpleClass { Id = 42, Name = "Test Object", Value = 3.14, IsActive = true };

        var binary = obj.ToBinary();
        var result = binary.BinaryTo<TestSimpleClass>();

        Assert.IsNotNull(result);
        Assert.AreEqual(obj.Id, result.Id);
        Assert.AreEqual(obj.Name, result.Name);
        Assert.AreEqual(obj.Value, result.Value);
        Assert.AreEqual(obj.IsActive, result.IsActive);
    }

    [TestMethod]
    public void Serialize_NestedObject_RoundTrip()
    {
        var obj = new TestNestedClass
        {
            Id = 1,
            Name = "Parent",
            Child = new TestSimpleClass { Id = 2, Name = "Child", Value = 2.5, IsActive = true }
        };

        var binary = obj.ToBinary();
        var result = binary.BinaryTo<TestNestedClass>();

        Assert.IsNotNull(result);
        Assert.AreEqual(obj.Id, result.Id);
        Assert.AreEqual(obj.Name, result.Name);
        Assert.IsNotNull(result.Child);
        Assert.AreEqual(obj.Child.Id, result.Child.Id);
        Assert.AreEqual(obj.Child.Name, result.Child.Name);
    }

    [TestMethod]
    public void Serialize_List_RoundTrip()
    {
        var list = new List<int> { 1, 2, 3, 4, 5 };
        var binary = list.ToBinary();
        var result = binary.BinaryTo<List<int>>();

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

    [TestMethod]
    public void Serialize_ObjectWithList_RoundTrip()
    {
        var obj = new TestClassWithList { Id = 1, Items = ["Item1", "Item2", "Item3"] };

        var binary = obj.ToBinary();
        var result = binary.BinaryTo<TestClassWithList>();

        Assert.IsNotNull(result);
        Assert.AreEqual(obj.Id, result.Id);
        Assert.IsNotNull(result.Items);
        CollectionAssert.AreEqual(obj.Items, result.Items);
    }

    [TestMethod]
    public void Serialize_Dictionary_RoundTrip()
    {
        var dict = new Dictionary<string, int> { ["one"] = 1, ["two"] = 2, ["three"] = 3 };

        var binary = dict.ToBinary();
        var result = binary.BinaryTo<Dictionary<string, int>>();

        Assert.IsNotNull(result);
        Assert.AreEqual(dict.Count, result.Count);
        foreach (var kvp in dict)
        {
            Assert.IsTrue(result.ContainsKey(kvp.Key));
            Assert.AreEqual(kvp.Value, result[kvp.Key]);
        }
    }

    [TestMethod]
    public void Populate_UpdatesExistingObject()
    {
        var target = new TestSimpleClass { Id = 0, Name = "Original" };
        var source = new TestSimpleClass { Id = 42, Name = "Updated", Value = 3.14 };

        var binary = source.ToBinary();
        binary.BinaryTo(target);

        Assert.AreEqual(42, target.Id);
        Assert.AreEqual("Updated", target.Name);
        Assert.AreEqual(3.14, target.Value);
    }

    [TestMethod]
    public void PopulateMerge_MergesNestedObjects()
    {
        var target = new TestNestedClass
        {
            Id = 1,
            Name = "Original",
            Child = new TestSimpleClass { Id = 10, Name = "OriginalChild", Value = 1.0 }
        };

        var source = new TestNestedClass
        {
            Id = 2,
            Name = "Updated",
            Child = new TestSimpleClass { Id = 20, Name = "UpdatedChild", Value = 2.0 }
        };

        var binary = source.ToBinary();
        binary.BinaryToMerge(target);

        Assert.AreEqual(2, target.Id);
        Assert.AreEqual("Updated", target.Name);
        Assert.IsNotNull(target.Child);
        Assert.AreEqual(20, target.Child.Id);
        Assert.AreEqual("UpdatedChild", target.Child.Name);
    }

    [TestMethod]
    public void Serialize_SimpleId45_RoundTrip()
    {
        // Simple test to debug Id=45 serialization issue
        var item = new TestHighReuseDto { Id = 45, CategoryCode = "test"};

        var binary = item.ToBinary();
        var result = binary.BinaryTo<TestHighReuseDto>();

        Assert.IsNotNull(result);
        Assert.AreEqual(45, result.Id, "Id should be 45 after deserialization");
        Assert.AreEqual("test", result.CategoryCode);
    }

    [TestMethod]
    public void Serialize_SimpleId0_RoundTrip()
    {
        // Test with Id=0 to see if SKIP marker is used correctly
        var item = new TestHighReuseDto { Id = 0 };

        var binary = item.ToBinary();
        var result = binary.BinaryTo<TestHighReuseDto>();

        Assert.IsNotNull(result);
        Assert.AreEqual(0, result.Id, "Id should be 0 after deserialization");
    }

    [TestMethod]
    [DataRow(-16, DisplayName = "TinyInt min: -16")]
    [DataRow(-1, DisplayName = "Negative: -1")]
    [DataRow(0, DisplayName = "Zero: 0")]
    [DataRow(1, DisplayName = "Small: 1")]
    [DataRow(45, DisplayName = "Was buggy: 45")]
    [DataRow(47, DisplayName = "TinyInt max: 47")]
    [DataRow(48, DisplayName = "Above TinyInt: 48")]
    [DataRow(66, DisplayName = "Was PropertySkip v2: 66")]
    [DataRow(100, DisplayName = "Medium: 100")]
    [DataRow(191, DisplayName = "Current PropertySkip value: 191")]
    [DataRow(192, DisplayName = "TinyInt code start: 192")]
    [DataRow(253, DisplayName = "Was PropertySkip v1: 253")]
    [DataRow(255, DisplayName = "Max byte: 255")]
    [DataRow(1000, DisplayName = "Large: 1000")]
    [DataRow(int.MaxValue, DisplayName = "MaxValue")]
    [DataRow(int.MinValue, DisplayName = "MinValue")]
    public void Serialize_VariousIntIds_PropertySkipTest(int id)
    {
        var item = new TestHighReuseDto { Id = id, CategoryCode = "test" };

        var binary = item.ToBinary();
        var result = binary.BinaryTo<TestHighReuseDto>();

        Assert.IsNotNull(result);
        Assert.AreEqual(id, result.Id, $"Id should be {id} after deserialization");
    }
}
