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

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for JSON Chain API (CreateDeserializeChain and CreatePopulateChain).
/// </summary>
[TestClass]
public class AcJsonSerializerChainTests
{
    [TestMethod]
    public void DeserializeChain_SingleDeserialization_WorksCorrectly()
    {
        // Arrange
        var original = new TestSimpleClass { Id = 42, Name = "John", Value = 3.14, IsActive = true };
        var json = original.ToJson();

        // Act
        using var chain = json.JsonToChain<TestSimpleClass>();
        var result = chain.Value;

        // Assert
        Assert.IsNotNull(result);
        Assert.AreEqual(42, result.Id);
        Assert.AreEqual("John", result.Name);
        Assert.AreEqual(3.14, result.Value);
        Assert.AreEqual(true, result.IsActive);
    }

    [TestMethod]
    public void DeserializeChain_MultipleDeserializations_ParsesOnlyOnce()
    {
        // Arrange
        var json = """{"Id":100,"Name":"Test","Value":99.9,"IsActive":false}""";

        // Act
        using var chain = json.JsonToChain<TestSimpleClass>();
        var result1 = chain.Value;
        var result2 = chain.ThenDeserialize<TestSimpleClass>();
        var result3 = chain.ThenDeserialize<TestSimpleClass>();

        // Assert - All three deserializations should work
        Assert.IsNotNull(result1);
        Assert.AreEqual(100, result1.Id);

        Assert.IsNotNull(result2);
        Assert.AreEqual(100, result2.Id);
        Assert.AreEqual("Test", result2.Name);

        Assert.IsNotNull(result3);
        Assert.AreEqual(99.9, result3.Value);
        Assert.AreEqual(false, result3.IsActive);
    }

    [TestMethod]
    public void DeserializeChain_DifferentTypes_WorksCorrectly()
    {
        // Arrange
        var json = """{"Id":1,"Name":"Product","Value":29.99}""";

        // Act
        using var chain = json.JsonToChain<TestSimpleClass>();
        var asClass = chain.Value;
        var asDict = chain.ThenDeserialize<Dictionary<string, object>>();

        // Assert
        Assert.IsNotNull(asClass);
        Assert.AreEqual(1, asClass.Id);
        Assert.AreEqual("Product", asClass.Name);

        Assert.IsNotNull(asDict);
        Assert.AreEqual(3, asDict.Count);
        Assert.IsTrue(asDict.ContainsKey("Id"));
        Assert.IsTrue(asDict.ContainsKey("Name"));
    }

    [TestMethod]
    public void DeserializeChain_NestedObjects_WorksCorrectly()
    {
        // Arrange
        var original = new TestNestedClass
        {
            Id = 1,
            Name = "Parent",
            Child = new TestSimpleClass { Id = 2, Name = "Child", Value = 10.5 }
        };
        var json = original.ToJson();

        // Act
        using var chain = json.JsonToChain<TestNestedClass>();
        var result1 = chain.Value;
        var result2 = chain.ThenDeserialize<TestNestedClass>();

        // Assert
        Assert.IsNotNull(result1);
        Assert.AreEqual("Parent", result1.Name);
        Assert.IsNotNull(result1.Child);
        Assert.AreEqual("Child", result1.Child.Name);

        Assert.IsNotNull(result2);
        Assert.AreEqual(1, result2.Id);
        Assert.IsNotNull(result2.Child);
        Assert.AreEqual(10.5, result2.Child.Value);
    }

    [TestMethod]
    public void DeserializeChain_WithList_WorksCorrectly()
    {
        // Arrange
        var original = new TestClassWithList
        {
            Id = 5,
            Items = new List<string> { "Apple", "Banana", "Cherry" }
        };
        var json = original.ToJson();

        // Act
        using var chain = json.JsonToChain<TestClassWithList>();
        var result = chain.Value;

        // Assert
        Assert.IsNotNull(result);
        Assert.AreEqual(5, result.Id);
        Assert.AreEqual(3, result.Items.Count);
        Assert.AreEqual("Apple", result.Items[0]);
        Assert.AreEqual("Banana", result.Items[1]);
        Assert.AreEqual("Cherry", result.Items[2]);
    }

    [TestMethod]
    public void PopulateChain_SinglePopulate_UpdatesObject()
    {
        // Arrange
        var json = """{"Id":99,"Name":"Updated","Value":123.45,"IsActive":true}""";
        var target = new TestSimpleClass { Id = 1, Name = "Old", Value = 0, IsActive = false };

        // Act
        using var chain = json.JsonToChain(target);

        // Assert
        Assert.AreEqual(99, target.Id);
        Assert.AreEqual("Updated", target.Name);
        Assert.AreEqual(123.45, target.Value);
        Assert.AreEqual(true, target.IsActive);
    }

    [TestMethod]
    public void PopulateChain_MultiplePopulates_UpdatesAllObjects()
    {
        // Arrange
        var json = """{"Id":100,"Name":"Shared","Value":50.0}""";
        var target1 = new TestSimpleClass { Id = 1, Name = "Old1" };
        var target2 = new TestSimpleClass { Id = 2, Name = "Old2" };
        var target3 = new TestSimpleClass { Id = 3, Name = "Old3" };

        // Act
        using var chain = json.JsonToChain(target1);
        chain.ThenPopulate(target2);
        chain.ThenPopulate(target3);

        // Assert
        Assert.AreEqual(100, target1.Id);
        Assert.AreEqual("Shared", target1.Name);
        Assert.AreEqual(50.0, target1.Value);

        Assert.AreEqual(100, target2.Id);
        Assert.AreEqual("Shared", target2.Name);
        Assert.AreEqual(50.0, target2.Value);

        Assert.AreEqual(100, target3.Id);
        Assert.AreEqual("Shared", target3.Name);
        Assert.AreEqual(50.0, target3.Value);
    }

    [TestMethod]
    public void PopulateChain_NestedObjects_MergesCorrectly()
    {
        // Arrange
        var json = """{"Id":10,"Name":"UpdatedParent","Child":{"Id":20,"Name":"UpdatedChild","Value":99.9}}""";
        var target = new TestNestedClass
        {
            Id = 1,
            Name = "OldParent",
            Child = new TestSimpleClass { Id = 2, Name = "OldChild", Value = 1.0 }
        };

        // Act
        using var chain = json.JsonToChain(target);

        // Assert
        Assert.AreEqual(10, target.Id);
        Assert.AreEqual("UpdatedParent", target.Name);
        Assert.IsNotNull(target.Child);
        Assert.AreEqual(20, target.Child.Id);
        Assert.AreEqual("UpdatedChild", target.Child.Name);
        Assert.AreEqual(99.9, target.Child.Value);
    }

    [TestMethod]
    public void PopulateChain_WithList_UpdatesCollection()
    {
        // Arrange
        var json = """{"Id":7,"Items":["New1","New2","New3"]}""";
        var target = new TestClassWithList
        {
            Id = 1,
            Items = new List<string> { "Old1" }
        };

        // Act
        using var chain = json.JsonToChain(target);

        // Assert
        Assert.AreEqual(7, target.Id);
        Assert.AreEqual(3, target.Items.Count);
        Assert.AreEqual("New1", target.Items[0]);
        Assert.AreEqual("New2", target.Items[1]);
        Assert.AreEqual("New3", target.Items[2]);
    }

    [TestMethod]
    public void DeserializeChain_EmptyJson_ReturnsEmpty()
    {
        // Arrange
        var json = "";

        // Act
        using var chain = json.JsonToChain<TestSimpleClass>();

        // Assert
        Assert.IsNull(chain.Value);
    }

    [TestMethod]
    public void DeserializeChain_NullJson_ReturnsEmpty()
    {
        // Arrange
        var json = "null";

        // Act
        using var chain = json.JsonToChain<TestSimpleClass>();

        // Assert
        Assert.IsNull(chain.Value);
    }

    [TestMethod]
    public void PopulateChain_EmptyJson_DoesNothing()
    {
        // Arrange
        var json = "";
        var target = new TestSimpleClass { Id = 42, Name = "Original" };

        // Act
        using var chain = json.JsonToChain(target);

        // Assert - Should remain unchanged
        Assert.AreEqual(42, target.Id);
        Assert.AreEqual("Original", target.Name);
    }

    [TestMethod]
    public void DeserializeChain_Dispose_CannotReuseAfterDispose()
    {
        // Arrange
        var json = """{"Id":1,"Name":"Test"}""";
        var chain = json.JsonToChain<TestSimpleClass>();
        var value = chain.Value;

        // Act
        chain.Dispose();

        // Assert
        Assert.IsNotNull(value); // Value from before dispose should still exist
        _ = Assert.ThrowsExactly<ObjectDisposedException>(() => chain.ThenDeserialize<TestSimpleClass>());
    }

    [TestMethod]
    public void PopulateChain_Dispose_CannotReuseAfterDispose()
    {
        // Arrange
        var json = """{"Id":1,"Name":"Test"}""";
        var target1 = new TestSimpleClass();
        var chain = json.JsonToChain(target1);

        // Act
        chain.Dispose();

        // Assert
        var target2 = new TestSimpleClass();
        _ = Assert.ThrowsExactly<ObjectDisposedException>(() => chain.ThenPopulate(target2));
    }

    [TestMethod]
    public void DeserializeChain_WithOptions_UsesCorrectOptions()
    {
        // Arrange
        var json = """{"Id":1,"Name":"Test","Value":10.5}""";
        var options = new AcJsonSerializerOptions { MaxDepth = 10 };

        // Act
        using var chain = json.JsonToChain<TestSimpleClass>(options);
        var result = chain.Value;

        // Assert
        Assert.IsNotNull(result);
        Assert.AreEqual(1, result.Id);
        Assert.AreEqual("Test", result.Name);
    }

    [TestMethod]
    public void PopulateChain_WithOptions_UsesCorrectOptions()
    {
        // Arrange
        var json = """{"Id":99,"Name":"Updated"}""";
        var target = new TestSimpleClass { Id = 1, Name = "Old" };
        var options = new AcJsonSerializerOptions { MaxDepth = 10 };

        // Act
        using var chain = json.JsonToChain(target, options);

        // Assert
        Assert.AreEqual(99, target.Id);
        Assert.AreEqual("Updated", target.Name);
    }
}
