using Jint.Native;
using Jint.Native.Json;

namespace Jint.Tests.Runtime
{
    public class JsonSerializerTests
    {
        [Fact]
        public void CanStringifyBasicTypes()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            Assert.Equal("null", serializer.Serialize(JsValue.Null).ToString());
            Assert.Equal("true", serializer.Serialize(JsBoolean.True).ToString());
            Assert.Equal("false", serializer.Serialize(JsBoolean.False).ToString());
            Assert.Equal("\"\"", serializer.Serialize(new JsString("")).ToString());
            Assert.Equal("\"abc\"", serializer.Serialize(new JsString("abc")).ToString());
            Assert.Equal("1", serializer.Serialize(new JsNumber(1)).ToString());
            Assert.Equal("0.5", serializer.Serialize(new JsNumber(0.5)).ToString());
            Assert.Equal("{}", serializer.Serialize(new JsObject(engine)).ToString());
            Assert.Equal("[]", serializer.Serialize(new JsArray(engine)).ToString());

            Assert.Same(JsValue.Undefined, serializer.Serialize(JsValue.Undefined));
        }

        [Fact]
        public void EmptyObjectHasNoLineBreakWithSpaceDefined()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);
            Assert.Equal("{}", serializer.Serialize(new JsObject(engine), JsValue.Undefined, new JsString("  ")).ToString());
        }

        [Fact]
        public void EmptyArrayHasNoLineBreakWithSpaceDefined()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);
            Assert.Equal("[]", serializer.Serialize(new JsArray(engine), JsValue.Undefined, new JsString("  ")).ToString());
        }

        [Fact]
        public void StringCharactersGetEscaped()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            string actual = serializer.Serialize(new JsString("\"\\\t\r\n\f\r\b\ud834")).ToString();
            Assert.Equal("\"\\\"\\\\\\t\\r\\n\\f\\r\\b\\ud834\"", actual);
        }

        [Fact]
        public void JsonStringOutputIsIndentedWhenSpacerDefined()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            JsObject instance = new JsObject(engine);
            instance["a"] = "b";
            instance["b"] = 2;
            instance["c"] = new JsArray(engine, new JsValue[] { new JsNumber(4), new JsNumber(5), new JsNumber(6) });
            instance["d"] = true;

            string actual = serializer.Serialize(instance, JsValue.Undefined, new JsNumber(2)).ToString();
            Assert.Equal("{\n  \"a\": \"b\",\n  \"b\": 2,\n  \"c\": [\n    4,\n    5,\n    6\n  ],\n  \"d\": true\n}", actual);
        }

        [Fact]
        public void JsonStringOutputIsCompactWithoutSpacer()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            JsObject instance = new JsObject(engine);
            instance["a"] = "b";
            instance["b"] = 2;
            instance["c"] = new JsArray(engine, new JsValue[] { new JsNumber(4), new JsNumber(5), new JsNumber(6) });
            instance["d"] = true;

            string actual = serializer.Serialize(instance, JsValue.Undefined, JsValue.Undefined).ToString();
            Assert.Equal("{\"a\":\"b\",\"b\":2,\"c\":[4,5,6],\"d\":true}", actual);
        }

        [Fact]
        public void ArrayWithUndefinedWillBeNull()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            JsArray array = new JsArray(engine, new JsValue[] { JsValue.Undefined, new JsNumber(42) });
            string actual = serializer.Serialize(array, JsValue.Undefined, JsValue.Undefined).ToString();
            Assert.Equal("[null,42]", actual);
        }

        [Fact]
        public void ObjectPropertyWithUndefinedWillBeSkipped()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            JsObject instance = new JsObject(engine);
            instance["a"] = JsValue.Undefined;
            instance["b"] = 42;
            string actual = serializer.Serialize(instance, JsValue.Undefined, JsValue.Undefined).ToString();
            Assert.Equal("{\"b\":42}", actual);
        }

        [Fact]
        public void NonStringObjectKeyWillSerializedAsString()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            JsObject instance = new JsObject(engine);
            instance[JsValue.Undefined] = 10;
            instance[JsValue.Null] = 21;
            instance[new JsNumber(10)] = 42;
            string actual = serializer.Serialize(instance, JsValue.Undefined, JsValue.Undefined).ToString();
            Assert.Equal("{\"10\":42,\"undefined\":10,\"null\":21}", actual);
        }

        [Fact]
        public void InfinityAndNaNGetsSerializedAsNull()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);
            JsArray array = new JsArray(engine, new JsValue[] { JsNumber.DoubleNegativeInfinity, JsNumber.DoublePositiveInfinity, JsNumber.DoubleNaN });
            string actual = serializer.Serialize(array, JsValue.Undefined, JsValue.Undefined).ToString();
            Assert.Equal("[null,null,null]", actual);
        }

        [Fact]
        public void ArrayAsReplacedDictatesPropertiesToSerializer()
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            JsObject instance = new JsObject(engine);
            instance["a"] = 21;
            instance["b"] = 42;
            JsValue replacer = new JsArray(engine, new JsValue[] { new JsString("b"), new JsString("z") });
            string actual = serializer.Serialize(instance, replacer, JsValue.Undefined).ToString();
            Assert.Equal("{\"b\":42}", actual);
        }

        [Theory]
        [InlineData("test123\n456", "\"test123\\n456\"")]
        [InlineData("test123456\n", "\"test123456\\n\"")]
        [InlineData("\u0002test\u0002", "\"\\u0002test\\u0002\"")]
        [InlineData("\u0002tes\tt\u0002", "\"\\u0002tes\\tt\\u0002\"")]
        [InlineData("t\u0002est\u0002", "\"t\\u0002est\\u0002\"")]
        [InlineData("test😀123456\n", "\"test😀123456\\n\"")]
        public void JsonStringEncodingFormatsContentCorrectly(string inputString, string expectedOutput)
        {
            using var engine = new Engine();
            var serializer = new JsonSerializer(engine);

            string actual = serializer.Serialize(new JsString(inputString)).ToString();
            Assert.Equal(expectedOutput, actual);
        }
    }
}
