using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
using ZB.MOM.WW.Secrets.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests;

/// <summary>
///     G-2b — pins <see cref="OpcUaClientSecretResolution.ResolveSecretRefsAsync"/>, the Layer-B
///     arm that resolves <c>secret:</c>-prefixed <see cref="OpcUaClientDriverOptions.Password"/>
///     and <see cref="OpcUaClientDriverOptions.UserCertificatePassword"/> through the shared
///     <see cref="ISecretResolver"/> just before the async session-open consumes them. The
///     <c>secret:</c> arm is fail-closed — an absent secret throws rather than leaving the
///     <c>secret:</c> literal in place (which would be sent verbatim as the password).
///     Non-secret literals and null/empty pass through unchanged.
/// </summary>
public sealed class OpcUaClientSecretResolutionTests
{
    private const string KnownPwName = "opcua/inst/password";
    private const string KnownPwValue = "resolved-password-plaintext";
    private const string KnownCertPwName = "opcua/inst/cert-pw";
    private const string KnownCertPwValue = "resolved-cert-pw-plaintext";

    /// <summary>A fake resolver: returns known values for two known names, null otherwise.</summary>
    private static ISecretResolver FakeResolver() => new StubSecretResolver();

    /// <summary>(a) A <c>secret:</c> Password resolves to the store's plaintext.</summary>
    [Fact]
    public async Task Secret_password_resolves_to_store_plaintext()
    {
        var options = new OpcUaClientDriverOptions { Password = $"secret:{KnownPwName}" };

        var resolved = await OpcUaClientSecretResolution.ResolveSecretRefsAsync(
            options, FakeResolver(), CancellationToken.None);

        resolved.Password.ShouldBe(KnownPwValue);
    }

    /// <summary>(b) A <c>secret:</c> UserCertificatePassword resolves to the store's plaintext.</summary>
    [Fact]
    public async Task Secret_cert_password_resolves_to_store_plaintext()
    {
        var options = new OpcUaClientDriverOptions { UserCertificatePassword = $"secret:{KnownCertPwName}" };

        var resolved = await OpcUaClientSecretResolution.ResolveSecretRefsAsync(
            options, FakeResolver(), CancellationToken.None);

        resolved.UserCertificatePassword.ShouldBe(KnownCertPwValue);
    }

    /// <summary>(c) A non-secret literal Password and a null field pass through unchanged.</summary>
    [Fact]
    public async Task Literal_and_null_pass_through_unchanged()
    {
        var options = new OpcUaClientDriverOptions
        {
            Password = "plainpw",
            UserCertificatePassword = null,
        };

        var resolved = await OpcUaClientSecretResolution.ResolveSecretRefsAsync(
            options, FakeResolver(), CancellationToken.None);

        resolved.Password.ShouldBe("plainpw");
        resolved.UserCertificatePassword.ShouldBeNull();
    }

    /// <summary>(d) An unknown <c>secret:</c> ref (resolver returns null) is fail-closed — it throws
    ///     and never leaves the <c>secret:</c> literal in place.</summary>
    [Fact]
    public async Task Unknown_secret_ref_is_fail_closed()
    {
        var options = new OpcUaClientDriverOptions { Password = "secret:opcua/inst/absent" };

        var ex = await Should.ThrowAsync<InvalidOperationException>(() =>
            OpcUaClientSecretResolution.ResolveSecretRefsAsync(
                options, FakeResolver(), CancellationToken.None));

        ex.Message.ShouldContain("Password");
        ex.Message.ShouldContain("opcua/inst/absent");
    }

    /// <summary>A fake resolver: returns known values for two known names, null otherwise.</summary>
    private sealed class StubSecretResolver : ISecretResolver
    {
        /// <inheritdoc />
        public Task<string?> GetAsync(SecretName name, CancellationToken ct) =>
            Task.FromResult<string?>(name.Value switch
            {
                KnownPwName => KnownPwValue,
                KnownCertPwName => KnownCertPwValue,
                _ => null,
            });
    }
}
