/*
 * Copyright 2026 Bloomberg Finance LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef INCLUDED_BUILDBOXCOMMON_CREDENTIALHELPERAUTHENTICATOR
#define INCLUDED_BUILDBOXCOMMON_CREDENTIALHELPERAUTHENTICATOR

#include <chrono>
#include <grpcpp/security/credentials.h>
#include <optional>
#include <shared_mutex>
#include <string>
#include <unordered_map>

namespace buildboxcommon {

/**
 * gRPC metadata plugin that invokes credential helpers to obtain
 * authentication headers.
 *
 * Implements the credential helper protocol as specified in the
 * credential-helper specification. Credential helpers are external
 * executables that provide dynamic authentication credentials.
 *
 * The authenticator:
 * - Invokes the configured credential helper
 * - Caches credentials per URI with optional expiration
 * - Thread-safe for concurrent gRPC requests
 * - Lazy refresh (no background threads)
 */
class CredentialHelperAuthenticator : public grpc::MetadataCredentialsPlugin {
  public:
    /**
     * Construct a credential helper authenticator.
     *
     * @param helperPath Path to the credential helper executable
     */
    explicit CredentialHelperAuthenticator(const std::string &helperPath);

    ~CredentialHelperAuthenticator() override = default;

    // Delete copy and move constructors/operators
    CredentialHelperAuthenticator(const CredentialHelperAuthenticator &) =
        delete;
    CredentialHelperAuthenticator &
    operator=(const CredentialHelperAuthenticator &) = delete;
    CredentialHelperAuthenticator(CredentialHelperAuthenticator &&) = delete;
    CredentialHelperAuthenticator &
    operator=(CredentialHelperAuthenticator &&) = delete;

    /**
     * Indicates whether this plugin blocks when obtaining credentials.
     * Returns false as credential helpers should execute quickly.
     */
    bool IsBlocking() const override;

    /**
     * Obtain authentication metadata for a gRPC request.
     *
     * This method:
     * 1. Constructs the full service URI from service_url and method_name
     * 2. Checks the cache for valid (non-expired) credentials
     * 3. If cache miss/expired: invokes the appropriate credential helper
     * 4. Parses the helper's JSON response
     * 5. Caches the credentials ONLY if they have an expiration time
     * 6. Adds the headers to the metadata map
     *
     * Note: Credentials without an expiration time are NOT cached. The helper
     * will be invoked on every request for such credentials.
     *
     * @param service_url The gRPC service URL (e.g., "example.com")
     * @param method_name The gRPC method name (e.g., "/Service/Method")
     * @param channel_auth_context Auth context (unused)
     * @param metadata Output multimap to populate with authentication headers
     * @return grpc::Status::OK on success, UNAUTHENTICATED on failure
     */
    grpc::Status
    GetMetadata(grpc::string_ref service_url, grpc::string_ref method_name,
                const grpc::AuthContext &channel_auth_context,
                std::multimap<grpc::string, grpc::string> *metadata) override;

  private:
    /**
     * Cached credentials for a specific URI.
     */
    struct CachedCredentials {
        std::multimap<std::string, std::string> headers;
        std::optional<std::chrono::system_clock::time_point> expiresAt;
    };

    /**
     * Invoke a credential helper and parse its response.
     *
     * Executes the helper with "get" argument and sends the URI as JSON on
     * stdin. Parses the JSON response from stdout into headers and optional
     * expiration.
     *
     * @param helperPath Path to the credential helper executable
     * @param uri Full service URI
     * @param credentials Output parameter to populate with parsed credentials
     * @return true on success, false on error (logs details)
     */
    bool invokeHelper(const std::string &helperPath, const std::string &uri,
                      CachedCredentials *credentials);

    const std::string d_helperPath;

    // Thread-safe cache: URI -> CachedCredentials
    std::shared_mutex d_cacheMutex;
    std::unordered_map<std::string, CachedCredentials> d_cache;
};

} // namespace buildboxcommon

#endif
