using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Host.Drivers;

namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;

/// <summary>
///     Guards the Test Connect wiring contract: every driver type editable in the AdminUI must have
///     a registered <see cref="IDriverProbe"/>, resolvable from the same DI container that hosts the
///     <c>admin-operations</c> cluster singleton. The singleton is role-pinned to <c>admin</c>, so on
///     a split-role deployment (the MAIN cluster's admin-only nodes) the probes must be wired by the
///     admin path — not only the driver path — or every Test Connect button returns
///     "No probe registered for driver type X".
/// </summary>
public sealed class DriverProbeRegistrationTests
{
    // The canonical "all drivers" set — one entry per AdminUI typed driver page's DriverTypeKey.
    // Keep in sync with the IDriverProbe registrations in
    // src/Server/.../Host/Drivers/DriverFactoryBootstrap.AddOtOpcUaDriverProbes (the routed
    // *DriverPage.razor flow was retired in v3 Batch 2 — the driver-type keys now live in the /raw
    // modals, but the probe set is the authority for what the AdminUI can Test-connect).
    private static readonly string[] AdminUiDriverTypeKeys =
    [
        "Modbus",
        "AbCip",
        "AbLegacy",
        "S7",
        "TwinCat",               // page key; probe reports "TwinCAT" — must resolve case-insensitively
        "Focas",                 // page key; probe reports "FOCAS"  — must resolve case-insensitively
        "OpcUaClient",
        "GalaxyMxGateway",
        "Calculation",           // v3 Batch 2 pseudo-driver; CalculationProbe registered in DriverFactoryBootstrap
        "MTConnect",             // MTConnectProbe — read-only Agent driver; probe issues a bare GET /probe
    ];

    [Fact]
    public void AddOtOpcUaDriverProbes_registers_a_probe_for_every_AdminUI_driver_type()
    {
        var services = new ServiceCollection();
        services.AddOtOpcUaDriverProbes();

        using var sp = services.BuildServiceProvider();
        var probes = sp.GetServices<IDriverProbe>().ToList();

        // No duplicate DriverType — AdminOperationsActor builds a dictionary keyed by DriverType
        // (case-insensitive) and would throw on a duplicate key, crashing the singleton.
        var byType = probes.ToDictionary(p => p.DriverType, StringComparer.OrdinalIgnoreCase);

        foreach (var key in AdminUiDriverTypeKeys)
            byType.ContainsKey(key).ShouldBeTrue($"No IDriverProbe registered for AdminUI driver type '{key}'.");
    }

    [Fact]
    public void AddOtOpcUaDriverProbes_is_idempotent()
    {
        // A fused admin,driver node calls the registration from both the driver-factory path and the
        // admin path. TryAddEnumerable must de-dup so the probe set stays unique (else the actor's
        // ToDictionary throws).
        var services = new ServiceCollection();
        services.AddOtOpcUaDriverProbes();
        services.AddOtOpcUaDriverProbes();

        using var sp = services.BuildServiceProvider();
        var probes = sp.GetServices<IDriverProbe>().ToList();

        var distinctTypes = probes.Select(p => p.DriverType).Distinct(StringComparer.OrdinalIgnoreCase).Count();
        probes.Count.ShouldBe(distinctTypes, "Duplicate IDriverProbe registrations — TryAddEnumerable should de-dup.");
        distinctTypes.ShouldBe(AdminUiDriverTypeKeys.Length);
    }
}
