using System.Security.Cryptography;

namespace NATS.Server.Auth;

/// <summary>
/// Central authentication orchestrator that builds the appropriate authenticators
/// from NatsOptions and tries them in priority order matching the Go server:
/// NKeys > Users > Token > SimpleUserPassword.
/// Reference: golang/nats-server/server/auth.go — checkClientAuth, configureAuthentication.
/// </summary>
public sealed class AuthService
{
    private readonly List<IAuthenticator> _authenticators;
    private readonly string? _noAuthUser;
    private readonly Dictionary<string, User>? _usersMap;

    public bool IsAuthRequired { get; }
    public bool NonceRequired { get; }

    private AuthService(List<IAuthenticator> authenticators, bool authRequired, bool nonceRequired,
        string? noAuthUser, Dictionary<string, User>? usersMap)
    {
        _authenticators = authenticators;
        IsAuthRequired = authRequired;
        NonceRequired = nonceRequired;
        _noAuthUser = noAuthUser;
        _usersMap = usersMap;
    }

    public static AuthService Build(NatsOptions options)
    {
        var authenticators = new List<IAuthenticator>();
        var authRequired = false;
        var nonceRequired = false;
        Dictionary<string, User>? usersMap = null;

        // TLS certificate mapping (highest priority when enabled)
        if (options.TlsMap && options.TlsVerify && options.Users is { Count: > 0 })
        {
            authenticators.Add(new TlsMapAuthenticator(options.Users));
            authRequired = true;
        }

        // JWT / Operator mode (highest priority after TLS)
        if (options.TrustedKeys is { Length: > 0 } && options.AccountResolver is not null)
        {
            authenticators.Add(new JwtAuthenticator(options.TrustedKeys, options.AccountResolver));
            authRequired = true;
            nonceRequired = true;
        }

        if (options.ExternalAuth is { Enabled: true, Client: not null } externalAuth)
        {
            authenticators.Add(new ExternalAuthCalloutAuthenticator(externalAuth.Client, externalAuth.Timeout));
            authRequired = true;
        }

        if (options.ProxyAuth is { Enabled: true } proxyAuth)
        {
            authenticators.Add(new ProxyAuthenticator(proxyAuth));
            authRequired = true;
        }

        // Priority order (matching Go): NKeys > Users > Token > SimpleUserPassword

        if (options.NKeys is { Count: > 0 })
        {
            authenticators.Add(new NKeyAuthenticator(options.NKeys));
            authRequired = true;
            nonceRequired = true;
        }

        if (options.Users is { Count: > 0 })
        {
            authenticators.Add(new UserPasswordAuthenticator(options.Users));
            authRequired = true;
            usersMap = new Dictionary<string, User>(StringComparer.Ordinal);
            foreach (var u in options.Users)
                usersMap[u.Username] = u;
        }

        if (!string.IsNullOrEmpty(options.Authorization))
        {
            authenticators.Add(new TokenAuthenticator(options.Authorization));
            authRequired = true;
        }

        if (!string.IsNullOrEmpty(options.Username) && !string.IsNullOrEmpty(options.Password))
        {
            authenticators.Add(new SimpleUserPasswordAuthenticator(options.Username, options.Password));
            authRequired = true;
        }

        return new AuthService(authenticators, authRequired, nonceRequired, options.NoAuthUser, usersMap);
    }

    public AuthResult? Authenticate(ClientAuthContext context)
    {
        if (!IsAuthRequired)
            return new AuthResult { Identity = string.Empty };

        foreach (var authenticator in _authenticators)
        {
            var result = authenticator.Authenticate(context);
            if (result != null)
                return result;
        }

        if (_noAuthUser != null && IsNoCredentials(context))
            return ResolveNoAuthUser();

        return null;
    }

    private static bool IsNoCredentials(ClientAuthContext context)
    {
        var opts = context.Opts;
        return string.IsNullOrEmpty(opts.Username)
            && string.IsNullOrEmpty(opts.Password)
            && string.IsNullOrEmpty(opts.Token)
            && string.IsNullOrEmpty(opts.Nkey)
            && string.IsNullOrEmpty(opts.Sig)
            && string.IsNullOrEmpty(opts.JWT);
    }

    private AuthResult? ResolveNoAuthUser()
    {
        if (_noAuthUser == null)
            return null;

        if (_usersMap != null && _usersMap.TryGetValue(_noAuthUser, out var user))
        {
            return new AuthResult
            {
                Identity = user.Username,
                AccountName = user.Account,
                Permissions = user.Permissions,
                Expiry = user.ConnectionDeadline,
            };
        }

        return new AuthResult { Identity = _noAuthUser };
    }

    public byte[] GenerateNonce()
    {
        Span<byte> raw = stackalloc byte[11];
        RandomNumberGenerator.Fill(raw);
        return raw.ToArray();
    }

    public static bool ValidateMqttCredentials(
        string? configuredUsername,
        string? configuredPassword,
        string? providedUsername,
        string? providedPassword)
    {
        if (string.IsNullOrEmpty(configuredUsername) && string.IsNullOrEmpty(configuredPassword))
            return true;

        return string.Equals(configuredUsername, providedUsername, StringComparison.Ordinal)
            && string.Equals(configuredPassword, providedPassword, StringComparison.Ordinal);
    }

    public string EncodeNonce(byte[] nonce)
    {
        return Convert.ToBase64String(nonce)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }
}
