using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using NATS.Server.Auth;

namespace NATS.Server.Transport.Tests;

public class TlsMapAuthenticatorTests
{
    private static X509Certificate2 CreateSelfSignedCert(string cn)
    {
        using var rsa = RSA.Create(2048);
        var req = new CertificateRequest($"CN={cn}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        return req.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
    }

    private static X509Certificate2 CreateCertWithDn(string dn)
    {
        using var rsa = RSA.Create(2048);
        var req = new CertificateRequest(dn, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        return req.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
    }

    [Fact]
    public void Matches_user_by_cn()
    {
        var users = new List<User>
        {
            new() { Username = "alice", Password = "" },
        };
        var auth = new TlsMapAuthenticator(users);
        var cert = CreateSelfSignedCert("alice");

        var ctx = new ClientAuthContext
        {
            Opts = new Protocol.ClientOptions(),
            Nonce = [],
            ClientCertificate = cert,
        };

        var result = auth.Authenticate(ctx);
        result.ShouldNotBeNull();
        result.Identity.ShouldBe("alice");
    }

    [Fact]
    public void Returns_null_when_no_cert()
    {
        var users = new List<User>
        {
            new() { Username = "alice", Password = "" },
        };
        var auth = new TlsMapAuthenticator(users);

        var ctx = new ClientAuthContext
        {
            Opts = new Protocol.ClientOptions(),
            Nonce = [],
            ClientCertificate = null,
        };

        var result = auth.Authenticate(ctx);
        result.ShouldBeNull();
    }

    [Fact]
    public void Returns_null_when_cn_doesnt_match()
    {
        var users = new List<User>
        {
            new() { Username = "alice", Password = "" },
        };
        var auth = new TlsMapAuthenticator(users);
        var cert = CreateSelfSignedCert("bob");

        var ctx = new ClientAuthContext
        {
            Opts = new Protocol.ClientOptions(),
            Nonce = [],
            ClientCertificate = cert,
        };

        var result = auth.Authenticate(ctx);
        result.ShouldBeNull();
    }

    [Fact]
    public void Matches_by_full_dn_string()
    {
        var users = new List<User>
        {
            new() { Username = "CN=alice, O=TestOrg", Password = "" },
        };
        var auth = new TlsMapAuthenticator(users);
        var cert = CreateCertWithDn("CN=alice, O=TestOrg");

        var ctx = new ClientAuthContext
        {
            Opts = new Protocol.ClientOptions(),
            Nonce = [],
            ClientCertificate = cert,
        };

        var result = auth.Authenticate(ctx);
        result.ShouldNotBeNull();
        result.Identity.ShouldBe("CN=alice, O=TestOrg");
    }

    [Fact]
    public void Returns_permissions_from_matched_user()
    {
        var perms = new Permissions
        {
            Publish = new SubjectPermission { Allow = ["foo.>"] },
        };
        var users = new List<User>
        {
            new() { Username = "alice", Password = "", Permissions = perms },
        };
        var auth = new TlsMapAuthenticator(users);
        var cert = CreateSelfSignedCert("alice");

        var ctx = new ClientAuthContext
        {
            Opts = new Protocol.ClientOptions(),
            Nonce = [],
            ClientCertificate = cert,
        };

        var result = auth.Authenticate(ctx);
        result.ShouldNotBeNull();
        result.Permissions.ShouldNotBeNull();
        result.Permissions.Publish!.Allow!.ShouldContain("foo.>");
    }
}
