using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;

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

// v3 Batch-1 migration: OpcUaClientTagConfigModel's address key was renamed FullName → NodeId and
// its serialized JSON key moved from PascalCase "FullName" to camelCase "nodeId" (TagConfig no longer
// carries identity; the upstream node reference is an ordinary driver-address field). The tests below
// track that rename; the preserve-unknown-keys round-trip coverage (unrecognised keys + the TagModal
// isHistorized/historianTagname merge keys) is retained unchanged.
public sealed class OpcUaClientTagConfigModelTests
{
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("{}")]
    public void FromJson_returns_defaults_for_empty_input(string? json)
    {
        var m = OpcUaClientTagConfigModel.FromJson(json);

        m.NodeId.ShouldBe("");
    }

    [Fact]
    public void FromJson_reads_NodeId()
    {
        var m = OpcUaClientTagConfigModel.FromJson(
            """{"nodeId":"nsu=urn:srv;s=Line3.Temp"}""");

        m.NodeId.ShouldBe("nsu=urn:srv;s=Line3.Temp");
    }

    [Fact]
    public void Round_trip_preserves_NodeId()
    {
        var m = new OpcUaClientTagConfigModel { NodeId = "ns=2;s=Line3.Temp" };

        var json = m.ToJson();
        var m2 = OpcUaClientTagConfigModel.FromJson(json);

        m2.NodeId.ShouldBe("ns=2;s=Line3.Temp");
    }

    [Fact]
    public void ToJson_emits_camelCase_NodeId()
    {
        var m = new OpcUaClientTagConfigModel { NodeId = "ns=2;s=Line3.Temp" };

        var json = m.ToJson();

        // nodeId is the v3 address key — camelCase, replacing the legacy PascalCase FullName identity key.
        json.ShouldContain("\"nodeId\":\"ns=2;s=Line3.Temp\"");
        json.ShouldNotContain("\"FullName\"", Case.Sensitive);
    }

    [Fact]
    public void FromJson_then_ToJson_preserves_unknown_keys()
    {
        var json = OpcUaClientTagConfigModel
            .FromJson("""{"nodeId":"ns=2;s=X","samplingIntervalMs":250}""")
            .ToJson();

        json.ShouldContain("samplingIntervalMs");
        json.ShouldContain("250");
        // and the exposed field still round-trips
        json.ShouldContain("\"nodeId\":\"ns=2;s=X\"");
    }

    [Fact]
    public void FromJson_then_ToJson_preserves_TagModal_merged_history_keys()
    {
        // The TagModal-merge seam writes isHistorized/historianTagname at the TagConfig root; this model
        // does NOT model them, so they must survive a load→save untouched as preserved unknown keys.
        var json = OpcUaClientTagConfigModel
            .FromJson("""{"nodeId":"ns=2;s=X","isHistorized":true,"historianTagname":"Line3_Temp"}""")
            .ToJson();

        json.ShouldContain("\"isHistorized\":true");
        json.ShouldContain("\"historianTagname\":\"Line3_Temp\"");
        json.ShouldContain("\"nodeId\":\"ns=2;s=X\"");
    }

    [Fact]
    public void ToJson_trims_NodeId()
    {
        var json = new OpcUaClientTagConfigModel { NodeId = "  ns=2;s=X  " }.ToJson();

        json.ShouldContain("\"nodeId\":\"ns=2;s=X\"");
    }

    [Fact]
    public void Validate_returns_error_when_NodeId_blank()
    {
        new OpcUaClientTagConfigModel().Validate().ShouldNotBeNullOrEmpty();
        new OpcUaClientTagConfigModel { NodeId = "   " }.Validate().ShouldNotBeNullOrEmpty();
    }

    [Fact]
    public void Validate_returns_null_when_NodeId_present()
    {
        new OpcUaClientTagConfigModel { NodeId = "ns=2;s=X" }.Validate().ShouldBeNull();
    }
}
