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

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

/// <summary>
///     B2-WP8 drift guard: asserts the tag-config dispatch maps (<see cref="TagConfigEditorMap"/> and
///     <see cref="TagConfigValidator"/>) are keyed by the canonical <see cref="DriverTypeNames"/>
///     constants. Because the maps are <c>OrdinalIgnoreCase</c>, a drifted literal (e.g. the historical
///     <c>"TwinCat"</c>/<c>"Focas"</c>) still matched case-insensitively — so a plain lookup test cannot
///     catch drift. These tests resolve <em>through the constants themselves</em>, so if a map key ever
///     stops matching its <see cref="DriverTypeNames"/> constant (a rename on either side) the dispatch
///     goes dark and the test fails.
/// </summary>
public sealed class TagConfigDriverTypeNameGuardTests
{
    // Every DriverTypeNames constant that has a typed editor registered.
    //
    // Enumerated FROM the map rather than hand-written. The hand-written list this replaced guarded
    // against RENAMES but not against GAPS: a newly-added DriverTypeNames constant simply never appeared
    // here, so the guard stayed green while the map went unmapped — and it had already silently drifted,
    // omitting Galaxy, Sql and Mqtt. Pairing it with the coverage test below makes both directions real.
    public static TheoryData<string> EditorMappedDriverTypes()
    {
        var data = new TheoryData<string>();
        foreach (var t in DriverTypeNames.All.Where(t => TagConfigEditorMap.Resolve(t) is not null))
        {
            data.Add(t);
        }

        return data;
    }

    /// <summary>
    ///     Coverage direction: every declared driver type has a typed tag editor, except those documented as
    ///     having none. Without this, enumerating the map above proves only that the map agrees with itself.
    /// </summary>
    [Fact]
    public void Every_declared_driver_type_has_a_typed_tag_editor_or_is_documented_as_raw_json()
    {
        // Galaxy authors its tag reference as a dotted FullName through the Galaxy address picker rather
        // than a typed field editor, so it uses the generic raw-TagConfig-JSON textarea by design.
        string[] rawJsonByDesign = [DriverTypeNames.Galaxy];

        var missing = DriverTypeNames.All
            .Where(t => TagConfigEditorMap.Resolve(t) is null)
            .Except(rawJsonByDesign, StringComparer.OrdinalIgnoreCase)
            .OrderBy(t => t, StringComparer.Ordinal)
            .ToList();

        missing.ShouldBeEmpty(
            $"these driver types have no typed tag editor and are not documented as raw-JSON by design, so "
            + $"the /uns TagModal falls back to a bare JSON textarea for them: {string.Join(", ", missing)}");
    }

    [Theory]
    [MemberData(nameof(EditorMappedDriverTypes))]
    public void EditorMap_resolves_every_mapped_DriverTypeNames_constant(string driverType)
        => TagConfigEditorMap.Resolve(driverType).ShouldNotBeNull(
            $"TagConfigEditorMap must dispatch DriverTypeNames.{driverType} — key/constant drift.");

    // Drivers whose typed model has a required field: a REGISTERED validator returns a non-null error for
    // the empty config, whereas an unmapped/drifted key falls through to the null "unmapped ⇒ valid" branch.
    // So a non-null result here unambiguously proves the constant is still keyed in the validator map.
    public static TheoryData<string> ValidatorRequiredFieldDriverTypes() =>
    [
        DriverTypeNames.S7,
        DriverTypeNames.AbCip,
        DriverTypeNames.AbLegacy,
        DriverTypeNames.TwinCAT,
        DriverTypeNames.FOCAS,
        DriverTypeNames.OpcUaClient,
        DriverTypeNames.MTConnect,
    ];

    [Theory]
    [MemberData(nameof(ValidatorRequiredFieldDriverTypes))]
    public void Validator_dispatches_every_required_field_DriverTypeNames_constant(string driverType)
        => TagConfigValidator.Validate(driverType, "{}").ShouldNotBeNullOrEmpty(
            $"TagConfigValidator must dispatch DriverTypeNames.{driverType} — key/constant drift.");

    // The drift the WP8 rewire fixed: these two constants differ in case from the retired literals
    // ("TwinCat"/"Focas"). Pin them explicitly.
    [Fact]
    public void TwinCAT_and_FOCAS_constants_resolve_their_editors()
    {
        TagConfigEditorMap.Resolve(DriverTypeNames.TwinCAT).ShouldNotBeNull();
        TagConfigEditorMap.Resolve(DriverTypeNames.FOCAS).ShouldNotBeNull();
    }
}
