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

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for nullable value type serialization.
/// </summary>
[TestClass]
public class AcBinarySerializerNullableTests
{
    [TestMethod]
    public void Deserialize_NullableIntProperty_WithValue_RoundTrip()
    {
        var obj = new TestClassWithNullableProperties { Id = 42, NullableInt = 123, NullableIntNull = null };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(42, result.Id);
        Assert.AreEqual(123, result.NullableInt);
        Assert.IsNull(result.NullableIntNull);
    }

    [TestMethod]
    public void Deserialize_NullableDoubleProperty_WithValue_RoundTrip()
    {
        var obj = new TestClassWithNullableProperties { Id = 1, NullableDouble = 3.14159, NullableDoubleNull = null };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(3.14159, result.NullableDouble);
        Assert.IsNull(result.NullableDoubleNull);
    }

    [TestMethod]
    public void Deserialize_NullableDateTimeProperty_WithValue_RoundTrip()
    {
        var testDate = new DateTime(2024, 12, 25, 10, 30, 45, DateTimeKind.Utc);
        var obj = new TestClassWithNullableProperties { Id = 1, NullableDateTime = testDate, NullableDateTimeNull = null };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(testDate, result.NullableDateTime);
        Assert.IsNull(result.NullableDateTimeNull);
    }

    [TestMethod]
    public void Deserialize_NullableGuidProperty_WithValue_RoundTrip()
    {
        var testGuid = Guid.NewGuid();
        var obj = new TestClassWithNullableProperties { Id = 1, NullableGuid = testGuid, NullableGuidNull = null };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(testGuid, result.NullableGuid);
        Assert.IsNull(result.NullableGuidNull);
    }

    [TestMethod]
    public void Deserialize_NullableDecimalProperty_WithValue_RoundTrip()
    {
        var obj = new TestClassWithNullableProperties { Id = 1, NullableDecimal = 123456.789m, NullableDecimalNull = null };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(123456.789m, result.NullableDecimal);
        Assert.IsNull(result.NullableDecimalNull);
    }

    [TestMethod]
    public void Deserialize_NullableBoolProperty_WithValue_RoundTrip()
    {
        var obj = new TestClassWithNullableProperties { Id = 1, NullableBool = true, NullableBoolNull = null };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(true, result.NullableBool);
        Assert.IsNull(result.NullableBoolNull);
    }

    [TestMethod]
    public void Deserialize_NullableLongProperty_WithValue_RoundTrip()
    {
        var obj = new TestClassWithNullableProperties { Id = 1, NullableLong = 9876543210L, NullableLongNull = null };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(9876543210L, result.NullableLong);
        Assert.IsNull(result.NullableLongNull);
    }

    [TestMethod]
    public void Deserialize_AllNullablePropertiesWithValues_RoundTrip()
    {
        var testDate = new DateTime(2024, 6, 15, 14, 30, 0, DateTimeKind.Utc);
        var testGuid = Guid.Parse("12345678-1234-1234-1234-123456789abc");

        var obj = new TestClassWithNullableProperties
        {
            Id = 999,
            NullableInt = int.MaxValue,
            NullableIntNull = 42,
            NullableLong = long.MaxValue,
            NullableLongNull = 100L,
            NullableDouble = double.MaxValue,
            NullableDoubleNull = 2.5,
            NullableDecimal = decimal.MaxValue,
            NullableDecimalNull = 1.1m,
            NullableDateTime = testDate,
            NullableDateTimeNull = DateTime.UtcNow,
            NullableGuid = testGuid,
            NullableGuidNull = Guid.NewGuid(),
            NullableBool = false,
            NullableBoolNull = true
        };

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

        Assert.IsNotNull(result);
        Assert.AreEqual(obj.Id, result.Id);
        Assert.AreEqual(obj.NullableInt, result.NullableInt);
        Assert.AreEqual(obj.NullableIntNull, result.NullableIntNull);
        Assert.AreEqual(obj.NullableLong, result.NullableLong);
        Assert.AreEqual(obj.NullableLongNull, result.NullableLongNull);
        Assert.AreEqual(obj.NullableDouble, result.NullableDouble);
        Assert.AreEqual(obj.NullableDecimalNull, result.NullableDecimalNull);
        Assert.AreEqual(obj.NullableDateTime, result.NullableDateTime);
        Assert.AreEqual(obj.NullableGuid, result.NullableGuid);
        Assert.AreEqual(obj.NullableBool, result.NullableBool);
        Assert.AreEqual(obj.NullableBoolNull, result.NullableBoolNull);
    }

    [TestMethod]
    public void Deserialize_ObjectWithNestedNullableProperties_RoundTrip()
    {
        var obj = new TestParentWithNullableChild
        {
            Id = 1,
            Name = "Parent",
            Child = new TestClassWithNullableProperties
            {
                Id = 2,
                NullableInt = 100,
                NullableDouble = 5.5,
                NullableDateTime = DateTime.UtcNow,
                NullableGuid = Guid.NewGuid()
            }
        };

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

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

    [TestMethod]
    public void Deserialize_ListOfObjectsWithNullableProperties_RoundTrip()
    {
        var items = CreateNullablePropertyItems(10);
        var binary = items.ToBinary();
        var result = binary.BinaryTo<List<TestClassWithNullableProperties>>();

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

        for (int i = 0; i < 10; i++)
        {
            var original = items[i];
            var deserialized = result[i];

            Assert.AreEqual(original.Id, deserialized.Id, $"Id mismatch at index {i}");
            Assert.AreEqual(original.NullableInt, deserialized.NullableInt, $"NullableInt mismatch at index {i}");
            Assert.AreEqual(original.NullableDouble, deserialized.NullableDouble, $"NullableDouble mismatch at index {i}");
            Assert.AreEqual(original.NullableGuid, deserialized.NullableGuid, $"NullableGuid mismatch at index {i}");
        }
    }

    [TestMethod]
    public void Deserialize_StockTakingLikeHierarchy_WithNullableProperties_RoundTrip()
    {
        var stockTaking = CreateStockTaking(2, 2);

        var binary = stockTaking.ToBinary();
        var result = binary.BinaryTo<TestStockTaking>();

        Assert.IsNotNull(result);
        Assert.AreEqual(stockTaking.Id, result.Id);
        Assert.AreEqual(stockTaking.IsClosed, result.IsClosed);
        Assert.AreEqual(stockTaking.Creator, result.Creator);

        Assert.IsNotNull(result.StockTakingItems);
        Assert.AreEqual(2, result.StockTakingItems.Count);

        var item0 = result.StockTakingItems[0];
        Assert.IsNotNull(item0.StockTakingItemPallets);
        Assert.AreEqual(2, item0.StockTakingItemPallets.Count);
    }

    [TestMethod]
    public void Deserialize_ListOfStockTakingLikeEntities_RoundTrip()
    {
        var stockTakings = CreateStockTakingList(2, 1, 1);
        var binary = stockTakings.ToBinary();
        var result = binary.BinaryTo<List<TestStockTaking>>();

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

        Assert.AreEqual(1, result[0].Id);
        Assert.IsNotNull(result[0].StockTakingItems);
        Assert.AreEqual(1, result[0].StockTakingItems.Count);

        Assert.AreEqual(2, result[1].Id);
        Assert.IsNotNull(result[1].StockTakingItems);
    }
}
