using System.Security.Cryptography;

namespace ZB.MOM.WW.MxGateway.Server.Security.Authentication;

public sealed class ApiKeyVerifier(
    IApiKeyParser parser,
    IApiKeySecretHasher hasher,
    IApiKeyStore keyStore) : IApiKeyVerifier
{
    /// <summary>
    /// Verifies an API key from an authorization header asynchronously.
    /// </summary>
    /// <param name="authorizationHeader">Authorization header value.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>Verification result.</returns>
    public async Task<ApiKeyVerificationResult> VerifyAsync(
        string? authorizationHeader,
        CancellationToken cancellationToken)
    {
        if (!parser.TryParseAuthorizationHeader(authorizationHeader, out ParsedApiKey? parsedKey)
            || parsedKey is null)
        {
            return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.MissingOrMalformedCredentials);
        }

        ApiKeyRecord? storedKey = await keyStore.FindByKeyIdAsync(parsedKey.KeyId, cancellationToken)
            .ConfigureAwait(false);

        if (storedKey is null)
        {
            return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.KeyNotFound);
        }

        if (storedKey.RevokedUtc is not null)
        {
            return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.KeyRevoked);
        }

        byte[] presentedHash;
        try
        {
            presentedHash = hasher.HashSecret(parsedKey.Secret);
        }
        catch (ApiKeyPepperUnavailableException)
        {
            return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.PepperUnavailable);
        }

        if (!CryptographicOperations.FixedTimeEquals(presentedHash, storedKey.SecretHash))
        {
            return ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.SecretMismatch);
        }

        await keyStore.MarkKeyUsedAsync(storedKey.KeyId, DateTimeOffset.UtcNow, cancellationToken)
            .ConfigureAwait(false);

        return ApiKeyVerificationResult.Success(new ApiKeyIdentity(
            KeyId: storedKey.KeyId,
            KeyPrefix: storedKey.KeyPrefix,
            DisplayName: storedKey.DisplayName,
            Scopes: storedKey.Scopes,
            Constraints: storedKey.Constraints));
    }
}
