using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Expressions;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Tests.TestModels;

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for AcExpressionNode serialization.
/// These tests verify that the serializers properly handle expression nodes
/// with the new type-safe constant value storage.
/// </summary>
[TestClass]
public class AcExpressionNodeSerializationTests
{
    #region JSON Serializer Tests

    /// <summary>
    /// Tests that AcExpressionNode with type-safe properties can be JSON serialized.
    /// </summary>
    [TestMethod]
    public void AcJsonSerializer_WithAcExpressionNode_RoundTrip_Works()
    {
        // Arrange - Create an expression with a constant value
        System.Linq.Expressions.Expression<Func<TestOrderItem_All_True, bool>> filterExpression = 
            item => item.Quantity > 5;
        
        var expressionNode = AcExpressionConverter.ToNode(filterExpression);
        
        // Act - Serialize
        var json = AcJsonSerializer.Serialize(expressionNode);
        Console.WriteLine($"Serialized JSON: {json}");
        
        Assert.IsNotNull(json, "JSON should not be null");
        Assert.IsTrue(json.Length > 0, "JSON should not be empty");
        
        // Deserialize
        var deserialized = AcJsonDeserializer.Deserialize<AcExpressionNode>(json);
        Assert.IsNotNull(deserialized, "Deserialized node should not be null");
        
        // Rebuild and test
        var rebuiltExpression = AcExpressionRebuilder.FromNode<TestOrderItem_All_True, bool>(deserialized);
        var compiled = rebuiltExpression.Compile();
        
        var matchingItem = new TestOrderItem_All_True { Id = 1, Quantity = 10 };
        var nonMatchingItem = new TestOrderItem_All_True { Id = 2, Quantity = 3 };
        
        Assert.IsTrue(compiled(matchingItem), "Matching item should pass filter");
        Assert.IsFalse(compiled(nonMatchingItem), "Non-matching item should fail filter");
    }

    /// <summary>
    /// Tests that decimal constant values are preserved after JSON round-trip.
    /// </summary>
    [TestMethod]
    public void AcJsonSerializer_WithDecimalValue_PreservesType()
    {
        // Arrange - Node with decimal constant
        var node = new AcExpressionNode
        {
            NodeType = System.Linq.Expressions.ExpressionType.Constant,
            TypeName = "System.Decimal"
        };
        node.SetValue(99.99m);
        
        // Act - JSON round-trip
        var json = AcJsonSerializer.Serialize(node);
        Console.WriteLine($"JSON: {json}");
        
        var deserialized = AcJsonDeserializer.Deserialize<AcExpressionNode>(json);
        
        // Assert
        Assert.IsNotNull(deserialized);
        Assert.AreEqual(ConstantValueType.Decimal, deserialized.ValueType);
        
        var value = deserialized.GetValue();
        Assert.IsNotNull(value, "Value should not be null");
        Assert.AreEqual(typeof(decimal), value.GetType(), "Should preserve decimal type");
        Assert.AreEqual(99.99m, (decimal)value);
    }

    /// <summary>
    /// Tests that Guid constant values are preserved after JSON round-trip.
    /// </summary>
    [TestMethod]
    public void AcJsonSerializer_WithGuidValue_PreservesType()
    {
        // Arrange
        var testGuid = Guid.NewGuid();
        var node = new AcExpressionNode
        {
            NodeType = System.Linq.Expressions.ExpressionType.Constant,
            TypeName = "System.Guid"
        };
        node.SetValue(testGuid);
        
        // Act
        var json = AcJsonSerializer.Serialize(node);
        Console.WriteLine($"JSON: {json}");
        
        var deserialized = AcJsonDeserializer.Deserialize<AcExpressionNode>(json);
        
        // Assert
        Assert.IsNotNull(deserialized);
        Assert.AreEqual(ConstantValueType.Guid, deserialized.ValueType);
        
        var value = deserialized.GetValue();
        Assert.IsNotNull(value);
        Assert.AreEqual(typeof(Guid), value.GetType(), "Should preserve Guid type");
        Assert.AreEqual(testGuid, (Guid)value);
    }

    #endregion

    #region Binary Serializer Tests

    /// <summary>
    /// Tests AcBinarySerializer with AcExpressionNode round-trip.
    /// </summary>
    [TestMethod]
    public void AcBinarySerializer_WithAcExpressionNode_RoundTrip_Works()
    {
        // Arrange
        System.Linq.Expressions.Expression<Func<TestOrderItem_All_True, bool>> filterExpression = 
            item => item.Quantity > 5;
        
        var originalNode = AcExpressionConverter.ToNode(filterExpression);
        
        // Act - Binary round-trip
        var bytes = AcBinarySerializer.Serialize(originalNode);
        Console.WriteLine($"Binary size: {bytes.Length} bytes");
        
        var deserialized = AcBinaryDeserializer.Deserialize<AcExpressionNode>(bytes);
        
        // Assert
        Assert.IsNotNull(deserialized);
        Assert.AreEqual(originalNode.NodeType, deserialized.NodeType);
        
        // Rebuild and test
        var rebuiltExpression = AcExpressionRebuilder.FromNode<TestOrderItem_All_True, bool>(deserialized);
        var compiled = rebuiltExpression.Compile();
        
        var matchingItem = new TestOrderItem_All_True { Id = 1, Quantity = 10 };
        var nonMatchingItem = new TestOrderItem_All_True { Id = 2, Quantity = 3 };
        
        Assert.IsTrue(compiled(matchingItem), "Matching item should pass filter");
        Assert.IsFalse(compiled(nonMatchingItem), "Non-matching item should fail filter");
    }

    /// <summary>
    /// Tests AcBinarySerializer with decimal in typed property.
    /// </summary>
    [TestMethod]
    public void AcBinarySerializer_WithDecimalValue_PreservesType()
    {
        // Arrange
        var node = new AcExpressionNode
        {
            NodeType = System.Linq.Expressions.ExpressionType.Constant,
            TypeName = "System.Decimal"
        };
        node.SetValue(99.99m);
        
        // Act
        var bytes = AcBinarySerializer.Serialize(node);
        var deserialized = AcBinaryDeserializer.Deserialize<AcExpressionNode>(bytes);
        
        // Assert
        Assert.IsNotNull(deserialized);
        Assert.AreEqual(ConstantValueType.Decimal, deserialized.ValueType);
        
        var value = deserialized.GetValue();
        Assert.IsNotNull(value);
        Assert.AreEqual(typeof(decimal), value.GetType(), "Binary should preserve decimal type");
        Assert.AreEqual(99.99m, (decimal)value);
    }

    /// <summary>
    /// Tests complex expression with decimal comparison.
    /// </summary>
    [TestMethod]
    public void AcBinarySerializer_WithDecimalComparison_RoundTrip_Works()
    {
        // Arrange - Expression with captured decimal: item => item.UnitPrice > 99.99m
        var minPrice = 99.99m;
        System.Linq.Expressions.Expression<Func<TestOrderItem_All_True, bool>> filterExpression = 
            item => item.UnitPrice > minPrice;
        
        var originalNode = AcExpressionConverter.ToNode(filterExpression);

        // Act - Binary round-trip
        var bytes = AcBinarySerializer.Serialize(originalNode);
        var deserializedNode = AcBinaryDeserializer.Deserialize<AcExpressionNode>(bytes);

        // Assert - Rebuild and verify it still works with decimal comparison
        Assert.IsNotNull(deserializedNode);
        
        var rebuiltExpression = AcExpressionRebuilder.FromNode<TestOrderItem_All_True, bool>(deserializedNode);
        var compiledFilter = rebuiltExpression.Compile();
        
        var expensiveItem = new TestOrderItem_All_True { UnitPrice = 150m };
        var cheapItem = new TestOrderItem_All_True { UnitPrice = 50m };
        
        Assert.IsTrue(compiledFilter(expensiveItem), "Expensive item should pass filter");
        Assert.IsFalse(compiledFilter(cheapItem), "Cheap item should fail filter");
    }

    /// <summary>
    /// Tests enum constant values.
    /// </summary>
    [TestMethod]
    public void AcBinarySerializer_WithEnumValue_PreservesType()
    {
        // Arrange - Expression with enum comparison
        System.Linq.Expressions.Expression<Func<TestOrderItem_All_True, bool>> filterExpression = 
            item => item.Status == TestStatus.Completed;
        
        var originalNode = AcExpressionConverter.ToNode(filterExpression);

        // Act - Binary round-trip
        var bytes = AcBinarySerializer.Serialize(originalNode);
        var deserializedNode = AcBinaryDeserializer.Deserialize<AcExpressionNode>(bytes);

        // Assert
        Assert.IsNotNull(deserializedNode);
        
        var rebuiltExpression = AcExpressionRebuilder.FromNode<TestOrderItem_All_True, bool>(deserializedNode);
        var compiledFilter = rebuiltExpression.Compile();
        
        var completedItem = new TestOrderItem_All_True { Status = TestStatus.Completed };
        var pendingItem = new TestOrderItem_All_True { Status = TestStatus.Pending };
        
        Assert.IsTrue(compiledFilter(completedItem), "Completed item should pass filter");
        Assert.IsFalse(compiledFilter(pendingItem), "Pending item should fail filter");
    }

    #endregion

    #region SetValue/GetDynamicValue Tests

    [TestMethod]
    [DataRow(42, ConstantValueType.Int32)]
    [DataRow(long.MaxValue, ConstantValueType.Int64)]
    [DataRow(3.14, ConstantValueType.Double)]
    [DataRow(true, ConstantValueType.Boolean)]
    [DataRow("test", ConstantValueType.String)]
    public void SetValue_GetValue_RoundTrip_PreservesValue(object value, ConstantValueType expectedType)
    {
        // Arrange
        var node = new AcExpressionNode();
        
        // Act
        node.SetValue(value);
        var result = node.GetValue();
        
        // Assert
        Assert.AreEqual(expectedType, node.ValueType);
        Assert.AreEqual(value, result);
    }

    [TestMethod]
    public void SetValue_Null_SetsNullType()
    {
        var node = new AcExpressionNode();
        node.SetValue(null);
        
        Assert.AreEqual(ConstantValueType.Null, node.ValueType);
        Assert.IsNull(node.GetValue());
    }

    [TestMethod]
    public void SetValue_Guid_PreservesValue()
    {
        var guid = Guid.NewGuid();
        var node = new AcExpressionNode();
        node.SetValue(guid);
        
        Assert.AreEqual(ConstantValueType.Guid, node.ValueType);
        Assert.AreEqual(guid, node.GetValue());
    }

    [TestMethod]
    public void SetValue_DateTime_PreservesValue()
    {
        var dateTime = new DateTime(2024, 6, 15, 10, 30, 45, DateTimeKind.Utc);
        var node = new AcExpressionNode();
        node.SetValue(dateTime);
        
        Assert.AreEqual(ConstantValueType.DateTime, node.ValueType);
        Assert.AreEqual(dateTime, node.GetValue());
    }

    [TestMethod]
    public void SetValue_Decimal_PreservesValue()
    {
        var value = 123.456789m;
        var node = new AcExpressionNode();
        node.SetValue(value);
        
        Assert.AreEqual(ConstantValueType.Decimal, node.ValueType);
        Assert.AreEqual(value, node.GetValue());
    }

    [TestMethod]
    public void SetValue_Enum_PreservesValue()
    {
        var node = new AcExpressionNode();
        node.SetValue(TestStatus.Processing);
        
        Assert.AreEqual(ConstantValueType.Enum, node.ValueType);
        Assert.IsTrue(node.EnumTypeName!.StartsWith("AyCode.Core.Tests.TestModels.TestStatus"), 
            $"EnumTypeName should start with full type name, got: {node.EnumTypeName}");
        Assert.AreEqual(TestStatus.Processing, node.GetValue());
    }

    #endregion
}
