using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
using ZB.MOM.WW.Secrets.Abstractions;

namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Adapters;

// G-3 / Task 9: the MxGateway per-endpoint ApiKey may hold a `secret:<name>` reference
// resolved via ISecretResolver at connect time. Literal keys pass through unchanged
// (back-compat); an unresolvable / missing secret, or a secret ref with no resolver
// available, fails closed (no connection established with an empty/null key).
[Collection("DataConnectionManagerActor")]
public class MxGatewayDataConnectionSecretsTests
{
    private static Dictionary<string, string> Details(string apiKey) => new()
    {
        ["Endpoint"] = "http://gw:5000",
        ["ApiKey"] = apiKey,
        ["ClientName"] = "client-a",
        ["WriteUserId"] = "0",
        ["ReadTimeoutMs"] = "2000",
    };

    private sealed class FakeSecretResolver : ISecretResolver
    {
        private readonly string? _value;
        public FakeSecretResolver(string? value) => _value = value;
        public List<string> Requested { get; } = new();

        public Task<string?> GetAsync(SecretName name, CancellationToken ct)
        {
            Requested.Add(name.Value);
            return Task.FromResult(_value);
        }
    }

    [Fact]
    public async Task ConnectAsync_resolves_secret_ref_apikey()
    {
        var fake = new FakeMxGatewayClient();
        var resolver = new FakeSecretResolver("resolved-plaintext-key");
        var adapter = new MxGatewayDataConnection(fake, NullLogger<MxGatewayDataConnection>.Instance, resolver);

        await adapter.ConnectAsync(Details("secret:mxgateway/testconn/api-key"));

        Assert.Equal(ConnectionHealth.Connected, adapter.Status);
        Assert.NotNull(fake.ConnectedWith);
        Assert.Equal("resolved-plaintext-key", fake.ConnectedWith!.ApiKey);
        Assert.Contains("mxgateway/testconn/api-key", resolver.Requested);
    }

    [Fact]
    public async Task ConnectAsync_passes_literal_apikey_unchanged_and_does_not_call_resolver()
    {
        var fake = new FakeMxGatewayClient();
        var resolver = new FakeSecretResolver("SHOULD-NOT-BE-USED");
        var adapter = new MxGatewayDataConnection(fake, NullLogger<MxGatewayDataConnection>.Instance, resolver);

        await adapter.ConnectAsync(Details("literal-api-key"));

        Assert.Equal("literal-api-key", fake.ConnectedWith!.ApiKey);
        Assert.Empty(resolver.Requested);
    }

    [Fact]
    public async Task ConnectAsync_fails_closed_when_secret_unresolvable()
    {
        var fake = new FakeMxGatewayClient();
        var resolver = new FakeSecretResolver(null); // secret not found / tombstoned
        var adapter = new MxGatewayDataConnection(fake, NullLogger<MxGatewayDataConnection>.Instance, resolver);

        await Assert.ThrowsAsync<InvalidOperationException>(
            () => adapter.ConnectAsync(Details("secret:mxgateway/testconn/missing")));

        // Fail-closed: the underlying client was never connected and status is not Connected.
        Assert.Null(fake.ConnectedWith);
        Assert.NotEqual(ConnectionHealth.Connected, adapter.Status);
    }

    [Fact]
    public async Task ConnectAsync_fails_closed_on_secret_ref_with_no_resolver()
    {
        var fake = new FakeMxGatewayClient();
        var adapter = new MxGatewayDataConnection(fake, NullLogger<MxGatewayDataConnection>.Instance, secretResolver: null);

        await Assert.ThrowsAsync<InvalidOperationException>(
            () => adapter.ConnectAsync(Details("secret:mxgateway/testconn/api-key")));

        Assert.Null(fake.ConnectedWith);
        Assert.NotEqual(ConnectionHealth.Connected, adapter.Status);
    }

    // DI-threading seam (code-review follow-up): the four tests above construct the adapter
    // directly with an explicit resolver, so they don't prove the factory→adapter DI wiring
    // G-3 actually relies on. This test resolves IDataConnectionFactory from a real container
    // (AddDataConnectionLayer + a registered fake ISecretResolver, no AddZbSecrets), creates a
    // "MxGateway" adapter through it, and drives a secret:-ref ConnectAsync. The real client
    // then fails to reach a non-existent gateway (expected, ignored) — but resolution happens
    // BEFORE any connect attempt, so the fake resolver being invoked proves the greediest
    // 3-arg DataConnectionFactory ctor picked up the DI-registered resolver and threaded it in.
    // Had it been null, ConnectAsync would have thrown the "no ISecretResolver" fail-closed
    // path instead and the resolver would never have been called.
    [Fact]
    public async Task Factory_from_DI_threads_registered_ISecretResolver_into_MxGateway_adapter()
    {
        var fake = new FakeSecretResolver("resolved-from-di");
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
        services.AddDataConnectionLayer();
        services.AddSingleton<ISecretResolver>(fake);
        using var provider = services.BuildServiceProvider();

        var factory = provider.GetRequiredService<IDataConnectionFactory>();

        // Point at a fast-refusing loopback port so the real client's connect fails quickly.
        var details = Details("secret:mxgateway/testconn/api-key");
        details["Endpoint"] = "http://127.0.0.1:1";

        await using var adapter = factory.Create("MxGateway", details);
        Assert.IsType<MxGatewayDataConnection>(adapter);

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
        try
        {
            await adapter.ConnectAsync(details, cts.Token);
        }
        catch
        {
            // The real RealMxGatewayClient cannot reach 127.0.0.1:1 — irrelevant to this test.
        }

        // Resolution runs before the connect attempt: the resolver being invoked proves the
        // factory threaded the DI-registered ISecretResolver into the adapter.
        Assert.Contains("mxgateway/testconn/api-key", fake.Requested);
    }
}
