using NUnit.Framework;
using System;
using System.Linq;
using HyperstoneSdk;
using UnityEngine;

public class ConfigTests
{
    const string ValidJsonConfig = @"
        {
            ""n1"": 1,
            ""n2"": 0.2452,
            ""n3"": -0.25123,
            ""n4"": -1.45e-4,
            ""n5"": 961726397540555,
            ""n6"": -1,
            ""t"": true,
            ""f"": false,
            ""n"": null,
            ""a"": [1,2,3],
            ""o"": {""foo"": ""bar""},
            ""s"": ""hello"",
            ""extra"": ""some extra field""
        }
    ";

    [Test]
    public void TestConfigKeys()
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);
        var keys = config.Keys.ToArray();

        Assert.AreEqual(new string[]
        {
            "n1", "n2", "n3", "n4", "n5", "n6", "t", "f", "n", "a", "o", "s", "extra"
        }, keys);
    }

    [Test]
    public void TestConfigAsObject()
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);
        var parsed = config.AsObject<ParsedConfig>();

        Assert.IsNotNull(parsed);
        Assert.AreEqual(1, parsed.n1);
        Assert.AreEqual(0.2452f, parsed.n2);
        Assert.AreEqual(-0.25123f, parsed.n3);
        Assert.AreEqual(-1.45e-4f, parsed.n4);
        Assert.AreEqual(961726397540555L, parsed.n5);
        Assert.AreEqual(-1, parsed.n6);
        Assert.AreEqual(true, parsed.t);
        Assert.AreEqual(false, parsed.f);
        Assert.AreEqual(string.Empty, parsed.n); // Unity parses null to default (string? -> "")
        Assert.AreEqual(new int[] { 1, 2, 3 }, parsed.a);
        Assert.AreEqual("hello", parsed.s);
        Assert.AreEqual("bar", parsed.o.foo);
    }

    [Test]
    // key, is successful, value
    [TestCase("n1", true, 1)]
    [TestCase("n2", false, 0)]
    [TestCase("n3", false, 0)]
    [TestCase("n4", false, 0)]
    [TestCase("n5", false, 0)]
    [TestCase("n6", true, -1)]
    [TestCase("s", false, 0)]  // different type
    [TestCase("non-existent", false, 0)] // nonexistent field
    public void TestConfigTryGetInt(string key, bool expectedSuccess, int expectedValue)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetInt(key, out var value);
        Assert.AreEqual(expectedSuccess, isSuccess);
        Assert.AreEqual(expectedValue, value);
    }

    [Test]
    // key, is successful, value
    [TestCase("n1", true, 1L)]
    [TestCase("n2", false, 0)]
    [TestCase("n3", false, 0)]
    [TestCase("n4", false, 0)]
    [TestCase("n5", true, 961726397540555L)]
    [TestCase("n6", true, -1L)]
    [TestCase("s", false, 0)]  // different type
    [TestCase("non-existent", false, 0)] // nonexistent field
    public void TestConfigTryGetLong(string key, bool expectedSuccess, long expectedValue)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetLong(key, out var value);
        Assert.AreEqual(expectedSuccess, isSuccess);
        Assert.AreEqual(expectedValue, value);
    }

    [Test]
    // key, is successful, value
    [TestCase("n1", true, 1f)]
    [TestCase("n2", true, 0.2452f)]
    [TestCase("n3", true, -0.25123f)]
    [TestCase("n4", true, -1.45e-4f)]
    [TestCase("n5", true, 961726397540555f)]
    [TestCase("n6", true, -1f)]
    [TestCase("s", false, 0f)]  // different type
    [TestCase("non-existent", false, 0f)] // nonexistent field
    public void TestConfigTryGetFloat(string key, bool expectedSuccess, float expectedValue)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetFloat(key, out var value);
        Assert.AreEqual(expectedSuccess, isSuccess);
        Assert.AreEqual(expectedValue, value);
    }

    [Test]
    // key, is successful, value
    [TestCase("n1", true, 1.0)]
    [TestCase("n2", true, 0.2452)]
    [TestCase("n3", true, -0.25123)]
    [TestCase("n4", true, -1.45e-4)]
    [TestCase("n5", true, 961726397540555.0)]
    [TestCase("n6", true, -1.0)]
    [TestCase("s", false, 0.0)]  // different type
    [TestCase("non-existent", false, 0f)] // nonexistent field
    public void TestConfigTryGetDouble(string key, bool expectedSuccess, double expectedValue)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetDouble(key, out var value);
        Assert.AreEqual(expectedSuccess, isSuccess);
        Assert.AreEqual(expectedValue, value);
    }

    [Test]
    // key, is successful, value
    [TestCase("t", true, true)]
    [TestCase("f", true, false)]
    [TestCase("s", false, false)]  // different type
    [TestCase("non-existent", false, false)] // nonexistent field
    public void TestConfigTryGetBool(string key, bool expectedSuccess, bool expectedValue)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetBool(key, out var value);
        Assert.AreEqual(expectedSuccess, isSuccess);
        Assert.AreEqual(expectedValue, value);
    }

    [Test]
    // key, is successful, value
    [TestCase("n", false, "")]
    [TestCase("s", true, "hello")]
    [TestCase("n1", false, "")]  // different type
    [TestCase("non-existent", false, "")] // nonexistent field
    public void TestConfigTryGetString(string key, bool expectedSuccess, string expectedValue)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetString(key, out var value);
        Assert.AreEqual(expectedSuccess, isSuccess);
        Assert.AreEqual(expectedValue, value);
    }

    [Test]
    // key, is successful, value
    [TestCase("n1", true, "1")]
    [TestCase("n2", true, "0.2452")]
    [TestCase("n3", true, "-0.25123")]
    [TestCase("n4", true, "-1.45e-4")]
    [TestCase("n5", true, "961726397540555")]
    [TestCase("n6", true, "-1")]
    [TestCase("t", true, "true")]
    [TestCase("f", true, "false")]
    [TestCase("n", true, "null")]
    [TestCase("a", true, "[1,2,3]")]
    [TestCase("o", true, @"{""foo"": ""bar""}")]
    [TestCase("s", true, "\"hello\"")]
    [TestCase("extra", true, "\"some extra field\"")]
    [TestCase("non-existent", false, "")] // nonexistent field
    public void TestTryGetValueAsJsonString(string key, bool expectedSuccess, string expectedValue)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetValueAsJson(key, out var value);
        Assert.AreEqual(expectedSuccess, isSuccess);
        Assert.AreEqual(expectedValue, value);
    }

    [Test]
    // key, is successful, value
    [TestCase("o", true)]
    [TestCase("n1", false)]  // different type
    [TestCase("non-existent", false)] // nonexistent field
    public void TestConfigTryGetValueAsObject(string key, bool expectedSuccess)
    {
        var config = HyperstoneConfig.FromJson(ValidJsonConfig);

        var isSuccess = config.TryGetValueAsObject<FooBar>(key, out var _);
        Assert.AreEqual(expectedSuccess, isSuccess);
    }

    [Test]
    public void TestConfigSerialization()
    {
        var configA = HyperstoneConfig.FromJson(ValidJsonConfig);

        // Simulate Unity serialization (like domain reload)
        var json = JsonUtility.ToJson(configA);
        var configB = JsonUtility.FromJson<HyperstoneConfig>(json);

        Assert.IsNotNull(configB);
        Assert.AreEqual(configA.ToString(), configB.ToString());

        // Verify that tokens were restored and functionality is preserved
        Assert.AreEqual(configA.GetIntOrDefault("n1", 0), configB.GetIntOrDefault("n1", 0));
        Assert.AreEqual(configA.GetStringOrDefault("s", ""), configB.GetStringOrDefault("s", ""));
        Assert.AreEqual(configA.GetBoolOrDefault("t", false), configB.GetBoolOrDefault("t", false));

        var keysA = configA.Keys.OrderBy(k => k).ToArray();
        var keysB = configB.Keys.OrderBy(k => k).ToArray();
        CollectionAssert.AreEqual(keysA, keysB);
    }

    [Serializable]
    private class ParsedConfig
    {
        public int n1;
        public float n2;
        public float n3;
        public float n4;
        public long n5;
        public int n6;
        public bool t;
        public bool f;
        public string n;
        public int[] a;
        public string s;
        public FooBar o;
    }

    [Serializable]
    private class FooBar
    {
        public string foo;
    }
}