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

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

/// <summary>
/// Unit tests for the driver-agnostic <see cref="TagHistorizeConfig"/> merge helper — the pure seam the
/// TagModal uses to read/write the root <c>isHistorized</c> / <c>historianTagname</c> history keys on the
/// canonical TagConfig JSON WITHOUT disturbing the driver's own (typed-editor) fields. Both this helper and
/// every typed editor preserve unknown keys, so they compose over the same JSON blob.
/// </summary>
public sealed class TagHistorizeConfigTests
{
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("{}")]
    [InlineData("not json")]
    public void Read_returns_defaults_for_empty_or_malformed(string? json)
    {
        var h = TagHistorizeConfig.Read(json);

        h.IsHistorized.ShouldBeFalse();
        h.HistorianTagname.ShouldBe("");
    }

    [Fact]
    public void Read_reads_both_keys()
    {
        var h = TagHistorizeConfig.Read("""{"FullName":"ns=2;s=X","isHistorized":true,"historianTagname":"Line3_Temp"}""");

        h.IsHistorized.ShouldBeTrue();
        h.HistorianTagname.ShouldBe("Line3_Temp");
    }

    [Fact]
    public void Set_true_with_tagname_writes_both_camelCase_keys()
    {
        var json = TagHistorizeConfig.Set("""{"FullName":"ns=2;s=X"}""", isHistorized: true, historianTagname: "Line3_Temp");

        json.ShouldContain("\"isHistorized\":true");
        json.ShouldContain("\"historianTagname\":\"Line3_Temp\"");
        // The driver field is untouched.
        json.ShouldContain("\"FullName\":\"ns=2;s=X\"");
    }

    [Fact]
    public void Set_true_round_trips_through_Read()
    {
        var json = TagHistorizeConfig.Set("{}", isHistorized: true, historianTagname: "Reactor1.Temp");

        var h = TagHistorizeConfig.Read(json);
        h.IsHistorized.ShouldBeTrue();
        h.HistorianTagname.ShouldBe("Reactor1.Temp");
    }

    [Fact]
    public void Set_false_removes_isHistorized_key()
    {
        var json = TagHistorizeConfig.Set(
            """{"FullName":"ns=2;s=X","isHistorized":true,"historianTagname":"Line3_Temp"}""",
            isHistorized: false, historianTagname: "Line3_Temp");

        json.ShouldNotContain("isHistorized");
        json.ShouldContain("\"FullName\":\"ns=2;s=X\"");
    }

    [Fact]
    public void Set_blank_tagname_removes_historianTagname_key()
    {
        var json = TagHistorizeConfig.Set(
            """{"isHistorized":true,"historianTagname":"Old"}""",
            isHistorized: true, historianTagname: "   ");

        json.ShouldContain("\"isHistorized\":true");
        json.ShouldNotContain("historianTagname");
    }

    [Fact]
    public void Set_null_tagname_removes_historianTagname_key()
    {
        var json = TagHistorizeConfig.Set(
            """{"isHistorized":true,"historianTagname":"Old"}""",
            isHistorized: true, historianTagname: null);

        json.ShouldContain("\"isHistorized\":true");
        json.ShouldNotContain("historianTagname");
    }

    [Fact]
    public void Set_trims_historianTagname()
    {
        var json = TagHistorizeConfig.Set("{}", isHistorized: true, historianTagname: "  Line3_Temp  ");

        json.ShouldContain("\"historianTagname\":\"Line3_Temp\"");
    }

    [Fact]
    public void Set_preserves_driver_specific_unknown_keys()
    {
        var json = TagHistorizeConfig.Set(
            """{"register":40001,"scale":0.1,"nested":{"a":1}}""",
            isHistorized: true, historianTagname: "T");

        json.ShouldContain("register");
        json.ShouldContain("40001");
        json.ShouldContain("scale");
        json.ShouldContain("0.1");
        json.ShouldContain("nested");
        json.ShouldContain("\"isHistorized\":true");
        json.ShouldContain("\"historianTagname\":\"T\"");
    }

    [Fact]
    public void Set_on_malformed_input_starts_from_empty_object()
    {
        var json = TagHistorizeConfig.Set("not json", isHistorized: true, historianTagname: null);

        json.ShouldContain("\"isHistorized\":true");
        // No crash, valid object emitted.
        json.ShouldStartWith("{");
        json.ShouldEndWith("}");
    }

    /// <summary>
    /// Full seam: the history helper and a driver-typed editor compose over the same canonical TagConfig
    /// blob without clobbering each other. Setting the history keys, round-tripping through the
    /// OpcUaClient typed editor (FromJson → ToJson), then reading the history keys back recovers them
    /// intact — and the editor's own NodeId field survives the history merge.
    /// </summary>
    [Fact]
    public void History_keys_survive_a_typed_editor_round_trip()
    {
        var withHistory = TagHistorizeConfig.Set(
            """{"nodeId":"ns=2;s=X"}""", isHistorized: true, historianTagname: "TN");

        var afterEditor = OpcUaClientTagConfigModel.FromJson(withHistory).ToJson();

        var h = TagHistorizeConfig.Read(afterEditor);
        h.IsHistorized.ShouldBeTrue();
        h.HistorianTagname.ShouldBe("TN");
        OpcUaClientTagConfigModel.FromJson(afterEditor).NodeId.ShouldBe("ns=2;s=X");
    }
}
