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

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

/// <summary>
///     Pins the browse-picker → typed-editor round trip for MTConnect, the driver's <b>primary</b>
///     authoring path (a tag is committed from the universal browser, then confirmed/overridden in the
///     typed editor).
/// </summary>
/// <remarks>
///     <para>
///         The defect this guards: <see cref="RawBrowseCommitMapper.BuildTagConfig"/> had no MTConnect
///         branch, so a committed tag landed as the generic <c>{"address": "&lt;dataItemId&gt;"}</c>
///         while <see cref="MTConnectTagConfigModel"/> read only <c>fullName</c>. The data plane kept
///         working (the driver accepts all three id spellings), so the ONLY symptom was an empty
///         DataItem-id field in the editor — and saving from that state stamped <c>fullName: ""</c>
///         over the tag. Razor is exactly what this repo cannot unit-test, so both halves of the fix
///         are pinned here in plain C#.
///     </para>
///     <para>
///         Both halves are asserted: the mapper now writes the canonical <c>fullName</c> (fixes new
///         commits), and the model accepts <c>dataItemId</c>/<c>address</c> as fallbacks (fixes tags
///         already committed before the fix).
///     </para>
/// </remarks>
[Trait("Category", "Unit")]
public sealed class MTConnectBrowseCommitRoundTripTests
{
    [Fact]
    public void BuildTagConfig_writes_the_canonical_fullName_key_for_MTConnect()
    {
        var json = RawBrowseCommitMapper.BuildTagConfig(DriverTypeNames.MTConnect, "avail");

        var o = JsonNode.Parse(json)!.AsObject();
        o["fullName"]!.GetValue<string>().ShouldBe("avail");
        o.ContainsKey("address").ShouldBeFalse(
            "the generic fallback key must not be used now that MTConnect has a typed editor");
    }

    [Fact]
    public void A_browse_committed_tag_round_trips_through_the_typed_editor_model()
    {
        var committed = RawBrowseCommitMapper.BuildTagConfig(DriverTypeNames.MTConnect, "Xact");

        var model = MTConnectTagConfigModel.FromJson(committed);

        model.FullName.ShouldBe("Xact");
        model.Validate().ShouldBeNull("a browse-committed tag must open in the editor already valid");
        MTConnectTagConfigModel.FromJson(model.ToJson()).FullName.ShouldBe("Xact");
    }

    // ---- legacy blobs committed before the mapper fix -------------------------------------------

    [Theory]
    [InlineData("address")]
    [InlineData("dataItemId")]
    public void FromJson_accepts_the_drivers_alternate_id_spellings(string key)
    {
        var model = MTConnectTagConfigModel.FromJson($$"""{"{{key}}":"Ypos"}""");

        model.FullName.ShouldBe("Ypos");
        model.Validate().ShouldBeNull();
    }

    [Fact]
    public void FullName_wins_over_dataItemId_which_wins_over_address()
    {
        MTConnectTagConfigModel
            .FromJson("""{"fullName":"a","dataItemId":"b","address":"c"}""")
            .FullName.ShouldBe("a");

        MTConnectTagConfigModel
            .FromJson("""{"dataItemId":"b","address":"c"}""")
            .FullName.ShouldBe("b");
    }

    [Fact]
    public void A_blank_alias_does_not_shadow_a_populated_one()
        => MTConnectTagConfigModel
            .FromJson("""{"fullName":"   ","address":"c"}""")
            .FullName.ShouldBe("c");

    [Fact]
    public void Saving_a_legacy_blob_normalises_it_onto_fullName_and_drops_the_aliases()
    {
        var model = MTConnectTagConfigModel.FromJson("""{"address":"Zpos","units":"MILLIMETER"}""");

        var o = JsonNode.Parse(model.ToJson())!.AsObject();

        o["fullName"]!.GetValue<string>().ShouldBe("Zpos");
        o.ContainsKey("address").ShouldBeFalse(
            "leaving a second id spelling behind lets the two drift apart on the next edit");
        o.ContainsKey("dataItemId").ShouldBeFalse();
        // Unrelated keys are still preserved — normalisation is not a purge.
        o["units"]!.GetValue<string>().ShouldBe("MILLIMETER");
    }
}
