using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Binaries;

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for DateTime serialization in Binary format.
/// Covers edge cases like string-stored DateTime values in GenericAttribute-like scenarios.
/// </summary>
[TestClass]
public class AcBinaryDateTimeSerializationTests
{
    #region DateTime Direct Serialization

    [TestMethod]
    public void DateTime_RoundTrip_PreservesValue()
    {
        var original = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc);
        
        var binary = AcBinarySerializer.Serialize(original);
        var result = binary.BinaryTo<DateTime>();
        
        Assert.AreEqual(original, result);
        Assert.AreEqual(DateTimeKind.Utc, result.Kind);
    }

    [TestMethod]
    public void NullableDateTime_RoundTrip_PreservesValue()
    {
        DateTime? original = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc);
        
        var binary = AcBinarySerializer.Serialize(original);
        var result = binary.BinaryTo<DateTime?>();
        
        Assert.AreEqual(original, result);
    }

    [TestMethod]
    public void NullableDateTime_Null_RoundTrip_PreservesNull()
    {
        DateTime? original = null;
        
        var binary = AcBinarySerializer.Serialize(original);
        var result = binary.BinaryTo<DateTime?>();
        
        Assert.IsNull(result);
    }

    #endregion

    #region Object with DateTime Property

    [TestMethod]
    public void ObjectWithDateTime_RoundTrip_PreservesValue()
    {
        var original = new TestObjectWithDateTime
        {
            Id = 1,
            CreatedAt = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc),
            UpdatedAt = new DateTime(2025, 12, 31, 23, 59, 59, DateTimeKind.Utc)
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestObjectWithDateTime>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(original.Id, result.Id);
        Assert.AreEqual(original.CreatedAt, result.CreatedAt);
        Assert.AreEqual(original.UpdatedAt, result.UpdatedAt);
    }

    [TestMethod]
    public void ObjectWithNullableDateTime_Null_RoundTrip_PreservesNull()
    {
        var original = new TestObjectWithNullableDateTime
        {
            Id = 1,
            DateOfReceipt = null
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestObjectWithNullableDateTime>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(original.Id, result.Id);
        Assert.IsNull(result.DateOfReceipt);
    }

    [TestMethod]
    public void ObjectWithNullableDateTime_HasValue_RoundTrip_PreservesValue()
    {
        var original = new TestObjectWithNullableDateTime
        {
            Id = 1,
            DateOfReceipt = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc)
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestObjectWithNullableDateTime>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(original.Id, result.Id);
        Assert.AreEqual(original.DateOfReceipt, result.DateOfReceipt);
    }

    #endregion

    #region GenericAttribute-like Scenario (String stored DateTime)

    [TestMethod]
    public void GenericAttributeScenario_DateTimeAsString_PreservesValue()
    {
        var original = new TestGenericAttribute
        {
            Key = "DateOfReceipt",
            Value = "10/24/2025 00:27:00"
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestGenericAttribute>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(original.Key, result.Key);
        Assert.AreEqual(original.Value, result.Value);
    }

    [TestMethod]
    public void GenericAttributeScenario_ZeroValue_PreservesValue()
    {
        var original = new TestGenericAttribute
        {
            Key = "DateOfReceipt",
            Value = "0"
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestGenericAttribute>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(original.Key, result.Key);
        Assert.AreEqual("0", result.Value);
    }

    [TestMethod]
    public void GenericAttributeList_RoundTrip_PreservesAllValues()
    {
        var original = new List<TestGenericAttribute>
        {
            new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" },
            new() { Key = "SomeNumber", Value = "42" },
            new() { Key = "EmptyValue", Value = "" },
            new() { Key = "ZeroValue", Value = "0" }
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<List<TestGenericAttribute>>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(4, result.Count);
        
        Assert.AreEqual("DateOfReceipt", result[0].Key);
        Assert.AreEqual("10/24/2025 00:27:00", result[0].Value);
        
        Assert.AreEqual("SomeNumber", result[1].Key);
        Assert.AreEqual("42", result[1].Value);
        
        Assert.AreEqual("EmptyValue", result[2].Key);
        Assert.AreEqual("", result[2].Value);
        
        Assert.AreEqual("ZeroValue", result[3].Key);
        Assert.AreEqual("0", result[3].Value);
    }

    [TestMethod]
    public void ObjectWithGenericAttributes_RoundTrip_PreservesAllValues()
    {
        var original = new TestDtoWithGenericAttributes
        {
            Id = 123,
            Name = "Test Order",
            GenericAttributes =
            [
                new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" },
                new() { Key = "Priority", Value = "1" }
            ]
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestDtoWithGenericAttributes>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(123, result.Id);
        Assert.AreEqual("Test Order", result.Name);
        Assert.AreEqual(2, result.GenericAttributes.Count);
        
        var dateAttr = result.GenericAttributes.FirstOrDefault(x => x.Key == "DateOfReceipt");
        Assert.IsNotNull(dateAttr);
        Assert.AreEqual("10/24/2025 00:27:00", dateAttr.Value);
    }

    #endregion

    #region JSON vs Binary Comparison

    [TestMethod]
    public void GenericAttribute_JsonAndBinary_ProduceSameResult()
    {
        var original = new TestGenericAttribute
        {
            Key = "DateOfReceipt",
            Value = "10/24/2025 00:27:00"
        };
        
        var json = original.ToJson();
        var jsonResult = json.JsonTo<TestGenericAttribute>();
        
        var binary = original.ToBinary();
        var binaryResult = binary.BinaryTo<TestGenericAttribute>();
        
        Assert.IsNotNull(jsonResult);
        Assert.IsNotNull(binaryResult);
        
        Assert.AreEqual(jsonResult.Key, binaryResult.Key);
        Assert.AreEqual(jsonResult.Value, binaryResult.Value);
    }

    [TestMethod]
    public void DtoWithGenericAttributes_JsonAndBinary_ProduceSameResult()
    {
        var original = new TestDtoWithGenericAttributes
        {
            Id = 123,
            Name = "Test Order",
            GenericAttributes =
            [
                new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" },
                new() { Key = "ZeroValue", Value = "0" }
            ]
        };
        
        var json = original.ToJson();
        var jsonResult = json.JsonTo<TestDtoWithGenericAttributes>();
        
        var binary = original.ToBinary();
        var binaryResult = binary.BinaryTo<TestDtoWithGenericAttributes>();
        
        Assert.IsNotNull(jsonResult);
        Assert.IsNotNull(binaryResult);
        
        Assert.AreEqual(jsonResult.Id, binaryResult.Id);
        Assert.AreEqual(jsonResult.Name, binaryResult.Name);
        Assert.AreEqual(jsonResult.GenericAttributes.Count, binaryResult.GenericAttributes.Count);
        
        for (int i = 0; i < jsonResult.GenericAttributes.Count; i++)
        {
            Assert.AreEqual(jsonResult.GenericAttributes[i].Key, binaryResult.GenericAttributes[i].Key);
            Assert.AreEqual(jsonResult.GenericAttributes[i].Value, binaryResult.GenericAttributes[i].Value);
        }
    }

    #endregion

    #region Test Models

    public class TestObjectWithDateTime
    {
        public int Id { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }

    public class TestObjectWithNullableDateTime
    {
        public int Id { get; set; }
        public DateTime? DateOfReceipt { get; set; }
    }

    public class TestGenericAttribute
    {
        public string Key { get; set; } = "";
        public string Value { get; set; } = "";
    }

    public class TestDtoWithGenericAttributes
    {
        public int Id { get; set; }
        public string Name { get; set; } = "";
        public List<TestGenericAttribute> GenericAttributes { get; set; } = [];
    }

    #endregion

    #region Exact Production Scenario Test

    /// <summary>
    /// This test reproduces the exact production bug scenario:
    /// 1. Server sends Binary serialized response with GenericAttributes
    /// 2. Client deserializes the Binary response
    /// 3. Client accesses DateOfReceipt property which reads from GenericAttributes
    /// 4. CommonHelper2.To<DateTime> fails to parse the string value
    /// </summary>
    [TestMethod]
    public void ProductionScenario_GenericAttributeWithDateString_PreservesExactFormat()
    {
        // Arrange: Create DTO with GenericAttributes like in production
        var original = new TestDtoWithGenericAttributes
        {
            Id = 123,
            Name = "Test Order",
            GenericAttributes =
            [
                new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" },
                new() { Key = "Priority", Value = "1" },
                new() { Key = "SomeFlag", Value = "true" }
            ]
        };
        
        // Act: Binary round-trip (simulates server->client communication)
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestDtoWithGenericAttributes>();
        
        // Assert: The exact string value must be preserved
        Assert.IsNotNull(result);
        var dateAttr = result.GenericAttributes.FirstOrDefault(x => x.Key == "DateOfReceipt");
        Assert.IsNotNull(dateAttr, "DateOfReceipt attribute should exist");
        
        // This is the critical assertion - the EXACT string must be preserved
        Assert.AreEqual("10/24/2025 00:27:00", dateAttr.Value, 
            $"Expected '10/24/2025 00:27:00' but got '{dateAttr.Value}'");
        
        // Verify it can be parsed with US culture (which is how it was stored)
        Assert.IsTrue(DateTime.TryParse(dateAttr.Value, new System.Globalization.CultureInfo("en-US"), 
            System.Globalization.DateTimeStyles.None, out var parsedDate),
            $"Value '{dateAttr.Value}' should be parseable as US date format");
        
        Assert.AreEqual(new DateTime(2025, 10, 24, 0, 27, 0), parsedDate);
    }

    /// <summary>
    /// Test that verifies the exact bytes of the string are preserved.
    /// </summary>
    [TestMethod]
    public void ProductionScenario_StringWithSlashes_BytesArePreserved()
    {
        var original = "10/24/2025 00:27:00";
        var originalBytes = System.Text.Encoding.UTF8.GetBytes(original);
        
        // Serialize and deserialize
        var binary = AcBinarySerializer.Serialize(original);
        var result = binary.BinaryTo<string>();
        
        Assert.IsNotNull(result);
        var resultBytes = System.Text.Encoding.UTF8.GetBytes(result);
        
        // Compare byte-by-byte
        Assert.AreEqual(originalBytes.Length, resultBytes.Length, "String length changed after serialization");
        for (int i = 0; i < originalBytes.Length; i++)
        {
            Assert.AreEqual(originalBytes[i], resultBytes[i], 
                $"Byte at position {i} differs: expected {originalBytes[i]:X2} ('{(char)originalBytes[i]}'), got {resultBytes[i]:X2} ('{(char)resultBytes[i]}')");
        }
    }

    /// <summary>
    /// Test with large list of GenericAttributes to catch any edge cases.
    /// </summary>
    [TestMethod]
    public void ProductionScenario_ManyGenericAttributes_AllPreserved()
    {
        var original = new TestDtoWithGenericAttributes
        {
            Id = 999,
            Name = "Large Order",
            GenericAttributes = Enumerable.Range(0, 50).Select(i => new TestGenericAttribute
            {
                Key = $"Attribute_{i}",
                Value = i % 5 == 0 ? $"{i}/24/2025 00:00:00" : i.ToString()
            }).ToList()
        };
        
        var binary = original.ToBinary();
        var result = binary.BinaryTo<TestDtoWithGenericAttributes>();
        
        Assert.IsNotNull(result);
        Assert.AreEqual(50, result.GenericAttributes.Count);
        
        for (int i = 0; i < 50; i++)
        {
            var expectedValue = i % 5 == 0 ? $"{i}/24/2025 00:00:00" : i.ToString();
            Assert.AreEqual($"Attribute_{i}", result.GenericAttributes[i].Key);
            Assert.AreEqual(expectedValue, result.GenericAttributes[i].Value,
                $"Attribute_{i} value mismatch: expected '{expectedValue}', got '{result.GenericAttributes[i].Value}'");
        }
    }

    #endregion

    #region CommonHelper2.To<DateTime> Simulation Tests

    /// <summary>
    /// This test simulates what CommonHelper2.To<DateTime> does with various string values.
    /// It helps identify which values will cause the FormatException.
    /// </summary>
    [TestMethod]
    public void CommonHelperSimulation_ValidDateString_ParsesSuccessfully()
    {
        var dateString = "10/24/2025 00:27:00";
        
        // This is what CommonHelper2.To<DateTime> does internally
        var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(DateTime));
        Assert.IsTrue(converter.CanConvertFrom(typeof(string)));
        
        // With InvariantCulture
        var result = converter.ConvertFrom(null, System.Globalization.CultureInfo.InvariantCulture, dateString);
        Assert.IsNotNull(result);
        Assert.IsInstanceOfType(result, typeof(DateTime));
        var dt = (DateTime)result;
        // InvariantCulture interprets 10/24/2025 as October 24, 2025
        Assert.AreEqual(2025, dt.Year);
        Assert.AreEqual(10, dt.Month);
        Assert.AreEqual(24, dt.Day);
    }

    /// <summary>
    /// This test shows that "0" cannot be parsed as DateTime - this is the actual bug!
    /// </summary>
    [TestMethod]
    public void CommonHelperSimulation_ZeroString_ThrowsFormatException()
    {
        var invalidValue = "0";
        
        var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(DateTime));
        
        // This should throw FormatException - exactly what we see in production
        var threw = false;
        try
        {
            converter.ConvertFrom(null, System.Globalization.CultureInfo.InvariantCulture, invalidValue);
        }
        catch (FormatException)
        {
            threw = true;
        }
        
        Assert.IsTrue(threw, "Converting '0' to DateTime should throw FormatException");
    }

    /// <summary>
    /// Test various invalid DateTime strings that might be stored in GenericAttributes.
    /// </summary>
    [TestMethod]
    [DataRow("0")]
    [DataRow("null")]
    [DataRow("undefined")]
    [DataRow("N/A")]
    public void CommonHelperSimulation_InvalidDateStrings_ThrowFormatException(string invalidValue)
    {
        var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(DateTime));
        
        var threw = false;
        try
        {
            converter.ConvertFrom(null, System.Globalization.CultureInfo.InvariantCulture, invalidValue);
        }
        catch (FormatException)
        {
            threw = true;
        }
        
        Assert.IsTrue(threw, $"Converting '{invalidValue}' to DateTime should throw FormatException");
    }

    #endregion
}
