using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using HyperstoneSdk;

public class JsonTests
{
    [Test]
    [TestCase("\"\"", "")]
    [TestCase("\"  \"", "  ")]

    // ESCAPE HELL
    [TestCase("\"\\n\\t\\r\\b\\f\"", "\n\t\r\b\f")]
    [TestCase("\"\\\\\"", "\\")]
    [TestCase("\"\\\"\"", "\"")]
    [TestCase("\"/\"", "/")]
    [TestCase("\"\\/\"", "/")]

    // U-ESCAPE HELL
    [TestCase("\"\\u0000\"", "\x00")]
    [TestCase("\"\\u007f\"", "\x7f")]
    [TestCase("\"\\uD801\\udc37\"", "𐐷")]
    [TestCase("\"\\u005F\"", "_")]
    [TestCase("\"\\u005f\"", "_")]

    // Somewhat normal
    [TestCase("\"hello\"", "hello")]
    [TestCase("\"€𝄞\"", "€𝄞")]
    [TestCase("\"привет\"", "привет")]
    [TestCase("\"abcdefghijklmnopqrstuvwABCDEFGHIJKLMNOPQRSTUVW01234567890-_.,+-[](){}!@#$%^&*=`\"", "abcdefghijklmnopqrstuvwABCDEFGHIJKLMNOPQRSTUVW01234567890-_.,+-[](){}!@#$%^&*=`")]

    public void ParseValidQuotedJsonString(string json, string expected)
    {
        var sb = new StringBuilder();
        var pos = 0;
        var isSuccessful = Json.ParseQuotedString(json, ref pos, sb);
        var result = sb.ToString();
        Assert.AreEqual(pos, json.Length);
        Assert.IsTrue(isSuccessful);
        Assert.AreEqual(expected, result);
    }

    [Test]
    [TestCase("")] // empty
    [TestCase("\"")] // no closing
    [TestCase("\"abc")] // no closing
    [TestCase(" \"\"")] // start with ws
    [TestCase("\"\"\"")] // leftover
    [TestCase("\"\n\"")] // control char
    [TestCase("\"\x00\"")] // non-char
    [TestCase("\"\\\"")] // unclosed escape
    [TestCase("\"\\a\"")] // invalid escape    
    [TestCase("\"\\u\"")] // invalid u-escape
    [TestCase("\"\\u00A\"")] // invalid u-escape (incomplete)
    [TestCase("\"\\uqqqq\"")] // invalid u-escape (non hexadecimal)
    // [TestCase("\"\\udfaa\"")] // invalid u-escape (lone second surrogate)
    [TestCase("\"\\ud800\\ud800n\"")] // invalid u-escape (incomplete surrogate)
    public void InvalidQuotedJsonString(string json)
    {
        var sb = new StringBuilder();
        var pos = 0;
        var isSuccessful = Json.ParseQuotedString(json, ref pos, sb);

        isSuccessful = isSuccessful && pos == json.Length;

        var result = sb.ToString();
        Assert.IsFalse(isSuccessful);
    }

    [Test]
    [TestCase("{}", "", "")] // empty
    [TestCase("{\"\":1}", "", "t")] // empty key
    [TestCase(" \r\n\t { \r\n\t } \r\n\t ", "", "")] // empty with ws

    // basic type
    [TestCase("{\"a\":1}", "a", "t")]
    [TestCase("{\"a\":1.02}", "a", "t")]
    [TestCase("{\"a\":null}", "a", "t")]
    [TestCase("{\"a\":true}", "a", "t")]
    [TestCase("{\"a\":false}", "a", "t")]
    [TestCase("{\"a\":\"\"}", "a", "s")]
    [TestCase("{\"a\":[]}", "a", "a")]
    [TestCase("{\"a\":{}}", "a", "o")]


    [TestCase("{\"a\":1,\"b\":2}", "a,b", "tt")]
    [TestCase(" {  \"a\"  :  1  ,  \"b\"  :  2  } ", "a,b", "tt")]
    [TestCase("\r\n\t{\r\n\t\"a\"\r\n\t:\r\n\t1\r\n\t,\r\n\t\"b\"\r\n\t:\r\n\t2\r\n\t}\r\n\t", "a,b", "tt")] // whitespaces
    [TestCase("{\"a\":1,\"b\":[1,2,3],\"c\":{\"a\":1},\"d\":\"\"}", "a,b,c,d", "taos")]

    [TestCase("{\"a\":1,\"a\":2}", "a", "t")] // duplicate keys is ok

    public void ParseValidObject(string json, string expectedKeysStr, string expectedTokenTypesStr)
    {
        var tokens = new Dictionary<string, JsonToken>();
        var jsonSpan = json.AsSpan();
        var pos = 0;
        var sb = new StringBuilder();
        var result = Json.ParseObject(jsonSpan, ref pos, tokens, sb);

        Assert.IsTrue(result);
        Assert.AreEqual(json[pos - 1], '}'); // pos should point to the nex valid token

        if (!string.IsNullOrEmpty(expectedKeysStr))
        {
            var expectedKeys = expectedKeysStr.Split(',');
            var i = 0;
            foreach (var key in expectedKeys)
            {
                Assert.IsTrue(tokens.ContainsKey(key), $"Tokens should contain key {key}");

                var token = tokens[key];
                var tokenType = token.Type;
                var expectedTokenType = CharToTokenType(expectedTokenTypesStr[i++]);

                Assert.AreEqual(expectedTokenType, tokenType);

                Assert.IsTrue(token.Start >= 0);
                Assert.IsTrue(token.Start < token.End);
                Assert.IsTrue(token.End <= json.Length);
            }
        }
    }

    [Test]
    [TestCase("")]
    [TestCase("[]")]
    [TestCase("null")]
    [TestCase("{")]
    [TestCase("{:}")]
    [TestCase("{\"a\"::1}")]
    [TestCase("{a:1}")]
    [TestCase("{1:1}")]
    // [TestCase("{\"a\":1,}")] // allowed for now
    public void InvalidObject(string json)
    {
        var jsonSpan = json.AsSpan();
        var pos = 0;
        var result = Json.ParseObject(jsonSpan, ref pos, null, null);

        Assert.IsFalse(result);
    }

    [Test]
    [TestCase("[]", "")]
    [TestCase("[1]", "t")]
    [TestCase("[true]", "t")]
    [TestCase("[false]", "t")]
    [TestCase("[null]", "t")]
    [TestCase("[\"\"]", "s")]
    [TestCase("[[]]", "a")]
    [TestCase("[[],[[]]]", "aa")]
    [TestCase("[{}]", "o")]
    [TestCase(" \r\n\t [ \r\n\t 1 \r\n\t ] \r\n\t", "t")]
    [TestCase("[1, \"\", true, [], {}]", "tstao")]
    public void ParseValidArray(string json, string expectedTokenTypesStr)
    {
        var tokens = new List<JsonToken>();
        var jsonSpan = json.AsSpan();
        var pos = 0;
        var result = Json.ParseArray(jsonSpan, ref pos, tokens);

        Assert.IsTrue(result);
        Assert.AreEqual(json[pos - 1], ']'); // pos should point to the nex valid token
        Assert.AreEqual(expectedTokenTypesStr.Length, tokens.Count);

        if (!string.IsNullOrEmpty(expectedTokenTypesStr))
        {
            var i = 0;
            foreach (var token in tokens)
            {
                var tokenType = token.Type;
                var expectedTokenType = CharToTokenType(expectedTokenTypesStr[i++]);

                Assert.AreEqual(expectedTokenType, tokenType);

                Assert.IsTrue(token.Start >= 0);
                Assert.IsTrue(token.Start < token.End);
                Assert.IsTrue(token.End <= json.Length);
            }
        }
    }

    [Test]
    [TestCase("")]
    [TestCase("hello")]
    [TestCase("{}")]
    [TestCase("\r")]
    [TestCase("][")]
    [TestCase("[")]
    [TestCase("[:]")]
    [TestCase("[:,]")]
    [TestCase("[1:2]")]
    [TestCase("[\"1\":2]")]
    [TestCase("[\"]")]
    [TestCase("[\"]")]
    [TestCase("[,1]")]
    // [TestCase("[1,]")] // allowed for now
    public void InvalidArray(string json)
    {
        var tokens = new List<JsonToken>();
        var jsonSpan = json.AsSpan();
        var pos = 0;
        var result = Json.ParseArray(jsonSpan, ref pos, tokens);

        Assert.IsFalse(result);
    }

    [Test]
    [TestCase("true")]
    [TestCase("false")]
    [TestCase("null")]
    [TestCase("1")]
    [TestCase("19879861294076")]
    [TestCase("-19879861294076")]
    [TestCase("1.0")]
    [TestCase("3e10")]
    [TestCase("3e-10")]
    [TestCase("-3.123e-10")]
    public void ValidTerminal(string json)
    {
        var pos = 0;
        var result = Json.ParseNonStringTerminal(json.AsSpan(), ref pos);

        Assert.IsTrue(result);
        Assert.IsTrue(pos > 0);
        Assert.AreEqual(json.Length, pos);
    }

    [Test]
    [TestCase("")]
    [TestCase(" ")]
    [TestCase("\t\r\n")]
    [TestCase("[]")]
    [TestCase("{}")]
    [TestCase("[")]
    [TestCase("}")]
    [TestCase(",")]
    [TestCase(":")]
    [TestCase("NaN")]
    [TestCase("Null")]
    // [TestCase("True")]
    // [TestCase("False")]
    // [TestCase("Infinity")]
    // [TestCase("-Infinity")]
    public void InvalidTerminal(string json)
    {
        var pos = 0;
        var result = Json.ParseNonStringTerminal(json.AsSpan(), ref pos);
        Assert.IsFalse(result);
    }

    private JsonTokenType CharToTokenType(char c)
    {
        return c switch
        {
            't' => JsonTokenType.TerminalValue,
            's' => JsonTokenType.QuotedStringValue,
            'a' => JsonTokenType.ArrayValue,
            'o' => JsonTokenType.ObjectValue,
            _ => throw new ArgumentOutOfRangeException(),
        };
    }

    [Test]
    public void TestJsonBuilderArray()
    {
        var builder = new JsonBuilder();
        builder.WriteValue(new int[] { 1, 2, 3, 4 });
        var json = builder.ToString();
        Assert.AreEqual("[1,2,3,4]", json);
    }

    [Test]
    public void TestJsonBuilderList()
    {
        var builder = new JsonBuilder();
        builder.WriteValue(new List<object> {
            1,
            2,
            "hello",
            4.52,
            null,
            true,
            new object[] { null, 1, true, "foo" },
            new Dictionary<string, float>() { { "x", -0.1f } } });
        var json = builder.ToString();
        Assert.AreEqual("[1,2,\"hello\",4.52,null,true,[null,1,true,\"foo\"],{\"x\":-0.1}]", json);
    }

    [Test]
    public void TestJsonBuilderObject()
    {
        var builder = new JsonBuilder();
        builder.WriteValue(new Dictionary<string, object>()
        {
            { "i", 1 },
            { "s", "hello" },
            { "n", null },
            { "b", true },
            { "f", -0.24f },
            { "a", new float[] {0.1f, 0.2f, 0.3f} },
            { "o", new Dictionary<string, object>() { { "foo", "bar" } } },
        });
        var json = builder.ToString();
        Assert.AreEqual("{\"i\":1,\"s\":\"hello\",\"n\":null,\"b\":true,\"f\":-0.24,\"a\":[0.1,0.2,0.3],\"o\":{\"foo\":\"bar\"}}", json);
    }

    [Test]
    [TestCase("", "")]
    [TestCase("hello", "hello")]
    [TestCase("привет", "привет")]
    [TestCase("\t\r\n", "\\t\\r\\n")]
    [TestCase("\"", "\\\"")]
    public void TestJsonBuilderEscapedStrings(string original, string escaped)
    {
        var builder = new JsonBuilder();
        builder.WriteValue(new Dictionary<string, string>() {
            { original, original }
        });
        var json = builder.ToString();
        Assert.AreEqual($"{{\"{escaped}\":\"{escaped}\"}}", json);
    }
}
