using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared;

public class SchemaBuilderModelTests
{
    // ── Parse ─────────────────────────────────────────────────────────────────

    [Fact]
    public void Parse_Empty_ReturnsFallback()
    {
        var fallback = SchemaBuilderModel.NewObject();
        Assert.Same(fallback, SchemaBuilderModel.Parse(null, fallback));
        Assert.Same(fallback, SchemaBuilderModel.Parse("", fallback));
        Assert.Same(fallback, SchemaBuilderModel.Parse("   ", fallback));
    }

    [Fact]
    public void Parse_Malformed_ReturnsFallback()
    {
        var fallback = SchemaBuilderModel.NewObject();
        Assert.Same(fallback, SchemaBuilderModel.Parse("{not json", fallback));
        Assert.Same(fallback, SchemaBuilderModel.Parse("42", fallback));
    }

    [Fact]
    public void Parse_ObjectSchema_ExtractsPropertiesAndRequired()
    {
        const string json = """
            {"type":"object","properties":{
              "id":{"type":"integer"},
              "label":{"type":"string"},
              "active":{"type":"boolean"}
            },"required":["id","active"]}
            """;
        var node = SchemaBuilderModel.Parse(json, SchemaBuilderModel.NewObject());

        Assert.Equal("object", node.Type);
        Assert.Collection(node.Properties,
            p => { Assert.Equal("id", p.Name); Assert.Equal("integer", p.Schema.Type); Assert.True(p.Required); },
            p => { Assert.Equal("label", p.Name); Assert.Equal("string", p.Schema.Type); Assert.False(p.Required); },
            p => { Assert.Equal("active", p.Name); Assert.Equal("boolean", p.Schema.Type); Assert.True(p.Required); });
    }

    [Fact]
    public void Parse_ArrayOfPrimitive_PreservesItemType()
    {
        var node = SchemaBuilderModel.Parse(
            @"{""type"":""array"",""items"":{""type"":""integer""}}",
            SchemaBuilderModel.NewValue());

        Assert.Equal("array", node.Type);
        Assert.NotNull(node.Items);
        Assert.Equal("integer", node.Items!.Type);
    }

    [Fact]
    public void Parse_LegacyFlatArray_TranslatedToObjectSchema()
    {
        const string json = """[{"name":"x","type":"Integer"},{"name":"y","type":"String","required":false}]""";
        var node = SchemaBuilderModel.Parse(json, SchemaBuilderModel.NewObject());

        Assert.Equal("object", node.Type);
        Assert.Collection(node.Properties,
            p => { Assert.Equal("x", p.Name); Assert.Equal("integer", p.Schema.Type); Assert.True(p.Required); },
            p => { Assert.Equal("y", p.Name); Assert.Equal("string", p.Schema.Type); Assert.False(p.Required); });
    }

    [Fact]
    public void Parse_NestedObjects_Recurses()
    {
        const string json = """
            {"type":"object","properties":{
              "outer":{"type":"object","properties":{
                "inner":{"type":"integer"}
              },"required":["inner"]}
            }}
            """;
        var node = SchemaBuilderModel.Parse(json, SchemaBuilderModel.NewObject());

        var outer = Assert.Single(node.Properties);
        Assert.Equal("outer", outer.Name);
        Assert.Equal("object", outer.Schema.Type);
        var inner = Assert.Single(outer.Schema.Properties);
        Assert.Equal("inner", inner.Name);
        Assert.Equal("integer", inner.Schema.Type);
        Assert.True(inner.Required);
    }

    // ── Serialize ─────────────────────────────────────────────────────────────

    [Fact]
    public void Serialize_EmptyObject_OmitsRequired()
    {
        var node = new SchemaNode { Type = "object" };
        var json = SchemaBuilderModel.Serialize(node);
        Assert.Equal("""{"type":"object","properties":{}}""", json);
    }

    [Fact]
    public void Serialize_ObjectWithMixedRequired_EmitsOnlyRequiredNames()
    {
        var node = new SchemaNode { Type = "object" };
        node.Properties.Add(new SchemaProperty { Name = "id", Required = true, Schema = new SchemaNode { Type = "integer" } });
        node.Properties.Add(new SchemaProperty { Name = "label", Required = false, Schema = new SchemaNode { Type = "string" } });

        var json = SchemaBuilderModel.Serialize(node);
        Assert.Equal(
            """{"type":"object","properties":{"id":{"type":"integer"},"label":{"type":"string"}},"required":["id"]}""",
            json);
    }

    [Fact]
    public void Serialize_Array_IncludesItems()
    {
        var node = new SchemaNode { Type = "array", Items = new SchemaNode { Type = "string" } };
        Assert.Equal("""{"type":"array","items":{"type":"string"}}""", SchemaBuilderModel.Serialize(node));
    }

    [Fact]
    public void Serialize_PropertiesWithBlankName_Skipped()
    {
        var node = new SchemaNode { Type = "object" };
        node.Properties.Add(new SchemaProperty { Name = "", Schema = new SchemaNode { Type = "integer" } });
        node.Properties.Add(new SchemaProperty { Name = "valid", Schema = new SchemaNode { Type = "string" } });

        var json = SchemaBuilderModel.Serialize(node);
        Assert.Equal("""{"type":"object","properties":{"valid":{"type":"string"}},"required":["valid"]}""", json);
    }

    // ── Round-trip ────────────────────────────────────────────────────────────

    [Fact]
    public void RoundTrip_Parse_Then_Serialize_Stable()
    {
        const string original = """{"type":"object","properties":{"id":{"type":"integer"},"tags":{"type":"array","items":{"type":"string"}}},"required":["id"]}""";
        var node = SchemaBuilderModel.Parse(original, SchemaBuilderModel.NewObject());
        var roundTripped = SchemaBuilderModel.Serialize(node);
        Assert.Equal(original, roundTripped);
    }
}
