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

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

/// <summary>
/// Unit tests for <see cref="TagConfigJson.SetFullName"/> — the pure merge helper the TagModal's Galaxy
/// address-picked handler uses to apply a freshly-picked <c>tag_name.AttributeName</c> reference. Re-picking
/// a Galaxy address must update ONLY the address-derived <c>FullName</c> key and carry every other
/// user-edited field over verbatim — ESPECIALLY a manually-authored <c>alarm</c> object (FB-4 / B2). The
/// previous handler rebuilt the config from a bare <c>{"FullName":...}</c> blob, silently dropping the alarm.
/// </summary>
public sealed class GalaxyAddressRepickMergeTests
{
    [Fact]
    public void Repick_preserves_an_already_edited_alarm_object()
    {
        // The operator authored an alarm, then re-picks the bound attribute to fix a typo.
        const string current =
            """{"FullName":"Pump_001.OldAttr","alarm":{"alarmType":"LimitAlarm","severity":250,"historizeToAveva":false}}""";

        var merged = TagConfigJson.SetFullName(current, "Pump_001.NewAttr");

        // The new address is applied...
        var fullName = ReadFullName(merged);
        fullName.ShouldBe("Pump_001.NewAttr");
        // ...and the hand-edited alarm survives the re-pick intact.
        var alarm = NativeAlarmModel.FromJson(merged);
        alarm.IsAlarm.ShouldBeTrue();
        alarm.AlarmType.ShouldBe("LimitAlarm");
        alarm.Severity.ShouldBe(250);
        alarm.HistorizeToAveva.ShouldBe(false);
    }

    [Fact]
    public void Repick_with_no_alarm_just_sets_the_address()
    {
        var merged = TagConfigJson.SetFullName("""{"FullName":"Pump_001.OldAttr"}""", "Pump_001.NewAttr");

        ReadFullName(merged).ShouldBe("Pump_001.NewAttr");
        NativeAlarmModel.FromJson(merged).IsAlarm.ShouldBeFalse();
    }

    [Fact]
    public void Repick_preserves_other_user_edited_non_address_fields()
    {
        // History intent + an array shape + an arbitrary scaling key the operator set independently of the
        // address — all must survive a re-pick that only owns FullName.
        const string current =
            """{"FullName":"Pump_001.OldAttr","isHistorized":true,"historianTagname":"Line3_Temp","isArray":true,"arrayLength":8,"scale":0.1}""";

        var merged = TagConfigJson.SetFullName(current, "Pump_001.NewAttr");

        ReadFullName(merged).ShouldBe("Pump_001.NewAttr");

        var hist = TagHistorizeConfig.Read(merged);
        hist.IsHistorized.ShouldBeTrue();
        hist.HistorianTagname.ShouldBe("Line3_Temp");

        var arr = TagArrayConfig.Read(merged);
        arr.IsArray.ShouldBeTrue();
        arr.ArrayLength.ShouldBe(8u);

        merged.ShouldContain("\"scale\":0.1");
    }

    [Fact]
    public void Set_on_empty_or_malformed_input_starts_from_a_bare_FullName_object()
    {
        var merged = TagConfigJson.SetFullName("not json", "Pump_001.NewAttr");

        ReadFullName(merged).ShouldBe("Pump_001.NewAttr");
        merged.ShouldStartWith("{");
        merged.ShouldEndWith("}");
    }

    [Fact]
    public void Repick_with_alarm_flag_does_not_overwrite_existing_alarm_object()
    {
        // Mirrors the handler's alarm-typed re-pick: SeedDefaultAlarm(SetFullName(existing, addr)).
        // The operator authored an alarm, then re-picks an alarm-typed Galaxy attribute — the
        // SeedDefaultAlarm no-op guard must leave the edited alarm intact, not re-seed a default.
        const string current =
            """{"FullName":"Pump_001.OldAttr","alarm":{"alarmType":"LimitAlarm","severity":250,"historizeToAveva":false}}""";

        var merged = NativeAlarmModel.SeedDefaultAlarm(TagConfigJson.SetFullName(current, "Pump_001.NewAttr"));

        ReadFullName(merged).ShouldBe("Pump_001.NewAttr");
        var alarm = NativeAlarmModel.FromJson(merged);
        alarm.IsAlarm.ShouldBeTrue();
        alarm.AlarmType.ShouldBe("LimitAlarm");
        alarm.Severity.ShouldBe(250);
        alarm.HistorizeToAveva.ShouldBe(false);
    }

    [Fact]
    public void SetFullName_rejects_a_blank_reference()
    {
        Should.Throw<ArgumentException>(() => TagConfigJson.SetFullName("""{"FullName":"x"}""", "  "));
    }

    // Mirror of TagModal.ReadFullName — extracts FullName from a Galaxy TagConfig blob.
    private static string ReadFullName(string json)
    {
        using var doc = System.Text.Json.JsonDocument.Parse(json);
        return doc.RootElement.TryGetProperty("FullName", out var fn) ? fn.GetString() ?? "" : "";
    }
}
