using NATS.Server.Auth;
using NATS.Server.Protocol;

namespace NATS.Server.Tests;

public class ExternalAuthCalloutTests
{
    [Fact]
    public void External_callout_authenticator_can_allow_and_deny_with_timeout_and_reason_mapping()
    {
        var authenticator = new ExternalAuthCalloutAuthenticator(
            new FakeExternalAuthClient(),
            TimeSpan.FromMilliseconds(50));

        var allowed = authenticator.Authenticate(new ClientAuthContext
        {
            Opts = new ClientOptions { Username = "u", Password = "p" },
            Nonce = [],
        });
        allowed.ShouldNotBeNull();
        allowed.Identity.ShouldBe("u");

        var denied = authenticator.Authenticate(new ClientAuthContext
        {
            Opts = new ClientOptions { Username = "u", Password = "bad" },
            Nonce = [],
        });
        denied.ShouldBeNull();

        var timeout = new ExternalAuthCalloutAuthenticator(
            new SlowExternalAuthClient(TimeSpan.FromMilliseconds(200)),
            TimeSpan.FromMilliseconds(30));
        timeout.Authenticate(new ClientAuthContext
        {
            Opts = new ClientOptions { Username = "u", Password = "p" },
            Nonce = [],
        }).ShouldBeNull();
    }

    private sealed class FakeExternalAuthClient : IExternalAuthClient
    {
        public Task<ExternalAuthDecision> AuthorizeAsync(ExternalAuthRequest request, CancellationToken ct)
        {
            if (request is { Username: "u", Password: "p" })
                return Task.FromResult(new ExternalAuthDecision(true, "u", "A"));
            return Task.FromResult(new ExternalAuthDecision(false, Reason: "denied"));
        }
    }

    private sealed class SlowExternalAuthClient(TimeSpan delay) : IExternalAuthClient
    {
        public async Task<ExternalAuthDecision> AuthorizeAsync(ExternalAuthRequest request, CancellationToken ct)
        {
            await Task.Delay(delay, ct);
            return new ExternalAuthDecision(true, "slow");
        }
    }
}
