using System.Text.Json.Nodes;
using Shouldly;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
using Xunit;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;

public sealed class TagConfigJsonTests
{
    private enum Flavor { None, Vanilla, Chocolate }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("{not json")]
    [InlineData("[1,2]")]
    public void ParseOrNew_returns_empty_object_for_unusable_input(string? json)
    {
        var obj = TagConfigJson.ParseOrNew(json);

        obj.ShouldNotBeNull();
        obj.Count.ShouldBe(0);
    }

    [Fact]
    public void ParseOrNew_parses_an_object()
    {
        var obj = TagConfigJson.ParseOrNew("""{"foo":"bar"}""");

        obj.Count.ShouldBe(1);
        TagConfigJson.GetString(obj, "foo").ShouldBe("bar");
    }

    [Fact]
    public void RoundTrip_preserves_unknown_keys()
    {
        var obj = TagConfigJson.ParseOrNew("""{"foo":"bar","region":"X"}""");

        TagConfigJson.Set(obj, "region", "Y");
        var json = TagConfigJson.Serialize(obj);

        json.ShouldContain("\"foo\":\"bar\"");
        json.ShouldContain("\"region\":\"Y\"");
    }

    [Fact]
    public void GetString_returns_null_when_absent()
    {
        var obj = new JsonObject();

        TagConfigJson.GetString(obj, "missing").ShouldBeNull();
    }

    [Fact]
    public void GetInt_reads_an_int()
    {
        var obj = TagConfigJson.ParseOrNew("""{"register":40001}""");

        TagConfigJson.GetInt(obj, "register").ShouldBe(40001);
    }

    [Fact]
    public void GetInt_falls_back_when_absent()
    {
        var obj = new JsonObject();

        TagConfigJson.GetInt(obj, "register", fallback: 7).ShouldBe(7);
    }

    [Fact]
    public void GetInt_falls_back_when_value_is_a_non_scalar_node()
    {
        // A nested object/array under the key must not throw — GetInt returns the fallback.
        var obj = TagConfigJson.ParseOrNew("""{"register":{},"other":[1,2]}""");

        TagConfigJson.GetInt(obj, "register", fallback: 7).ShouldBe(7);
        TagConfigJson.GetInt(obj, "other", fallback: 9).ShouldBe(9);
    }

    [Fact]
    public void GetString_returns_null_when_value_is_a_non_string_node()
    {
        var obj = TagConfigJson.ParseOrNew("""{"register":40001,"nested":{}}""");

        TagConfigJson.GetString(obj, "register").ShouldBeNull();
        TagConfigJson.GetString(obj, "nested").ShouldBeNull();
    }

    [Fact]
    public void GetBool_reads_a_bool()
    {
        var obj = TagConfigJson.ParseOrNew("""{"isHistorized":true}""");

        TagConfigJson.GetBool(obj, "isHistorized").ShouldBeTrue();
    }

    [Fact]
    public void GetBool_falls_back_when_absent()
    {
        var obj = new JsonObject();

        TagConfigJson.GetBool(obj, "isHistorized", fallback: true).ShouldBeTrue();
    }

    [Fact]
    public void GetBool_falls_back_when_value_is_not_a_bool()
    {
        var obj = TagConfigJson.ParseOrNew("""{"isHistorized":"yes","other":1}""");

        TagConfigJson.GetBool(obj, "isHistorized").ShouldBeFalse();
        TagConfigJson.GetBool(obj, "other").ShouldBeFalse();
    }

    [Fact]
    public void GetEnum_parses_by_name_case_insensitive()
    {
        var obj = TagConfigJson.ParseOrNew("""{"flavor":"chocolate"}""");

        TagConfigJson.GetEnum(obj, "flavor", Flavor.None).ShouldBe(Flavor.Chocolate);
    }

    [Fact]
    public void GetEnum_falls_back_when_absent()
    {
        var obj = new JsonObject();

        TagConfigJson.GetEnum(obj, "flavor", Flavor.Vanilla).ShouldBe(Flavor.Vanilla);
    }

    [Fact]
    public void GetEnum_falls_back_on_garbage()
    {
        var obj = TagConfigJson.ParseOrNew("""{"flavor":"strawberry"}""");

        TagConfigJson.GetEnum(obj, "flavor", Flavor.Vanilla).ShouldBe(Flavor.Vanilla);
    }

    [Fact]
    public void Set_writes_an_enum_as_its_name_string()
    {
        var obj = new JsonObject();

        TagConfigJson.Set(obj, "flavor", Flavor.Chocolate);
        var json = TagConfigJson.Serialize(obj);

        json.ShouldContain($"\"flavor\":\"{Flavor.Chocolate}\"");
    }

    [Fact]
    public void Set_with_null_removes_the_key()
    {
        var obj = TagConfigJson.ParseOrNew("""{"region":"X"}""");

        TagConfigJson.Set(obj, "region", null);

        obj.ContainsKey("region").ShouldBeFalse();
        TagConfigJson.Serialize(obj).ShouldNotContain("region");
    }
}
