using System.Security.Cryptography;
using System.Text;

namespace NATS.Server.Auth;

/// <summary>
/// Authenticates a single username/password pair configured on the server.
/// Supports plain-text and bcrypt-hashed passwords.
/// Uses constant-time comparison for both username and password to prevent timing attacks.
/// Reference: golang/nats-server/server/auth.go checkClientAuth for single user.
/// </summary>
public sealed class SimpleUserPasswordAuthenticator : IAuthenticator
{
    private readonly byte[] _expectedUsername;
    private readonly string _serverPassword;

    public SimpleUserPasswordAuthenticator(string username, string password)
    {
        _expectedUsername = Encoding.UTF8.GetBytes(username);
        _serverPassword = password;
    }

    public AuthResult? Authenticate(ClientAuthContext context)
    {
        var clientUsername = context.Opts.Username;
        if (string.IsNullOrEmpty(clientUsername))
            return null;

        var clientUsernameBytes = Encoding.UTF8.GetBytes(clientUsername);
        if (!CryptographicOperations.FixedTimeEquals(clientUsernameBytes, _expectedUsername))
            return null;

        var clientPassword = context.Opts.Password ?? string.Empty;

        if (!ComparePasswords(_serverPassword, clientPassword))
            return null;

        return new AuthResult { Identity = clientUsername };
    }

    private static bool ComparePasswords(string serverPassword, string clientPassword)
    {
        // Bcrypt hashes start with "$2" (e.g., $2a$, $2b$, $2y$)
        if (serverPassword.StartsWith("$2"))
        {
            try
            {
                return BCrypt.Net.BCrypt.Verify(clientPassword, serverPassword);
            }
            catch
            {
                return false;
            }
        }

        // Plain-text: constant-time comparison to prevent timing attacks
        var serverBytes = Encoding.UTF8.GetBytes(serverPassword);
        var clientBytes = Encoding.UTF8.GetBytes(clientPassword);
        return CryptographicOperations.FixedTimeEquals(serverBytes, clientBytes);
    }
}
