/*
 * Copyright 2021 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_ASSETCLIENT_H
#define INCLUDED_BUILDBOXCOMMON_ASSETCLIENT_H

#include <buildboxcommon_connectionoptions.h>
#include <buildboxcommon_grpcclient.h>
#include <buildboxcommon_grpcretrier.h>
#include <buildboxcommon_protos.h>

#include <chrono>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

namespace buildboxcommon {

using namespace build::bazel::remote::asset::v1;

/**
 * Implements a mechanism to communicate with Remote Asset servers.
 */
class AssetClient // NOLINT (cppcoreguidelines-virtual-class-destructor)
{
  public:
    AssetClient(std::shared_ptr<GrpcClient> grpcClient)
        : d_grpcClient(std::move(grpcClient))
    {
    }

    virtual ~AssetClient() = default; // Add a virtual destructor

    /**
     * Connect to the Remote Asset server with the given connection options.
     */
    virtual void init();

    /**
     * Connect to the Remote Asset server with the given clients.
     */
    virtual void init(std::shared_ptr<Fetch::StubInterface> fetchClient,
                      std::shared_ptr<Push::StubInterface> pushClient);

    /* This is a custom POD type containing the result of fetchBlob() and
     * fetchDirectory(). It has most of the fields in the GRPC response
     * message, but converted to C++-friendly types. */
    struct FetchResult {
        std::string uri;
        std::vector<std::pair<std::string, std::string>> qualifiers;
        std::chrono::time_point<std::chrono::steady_clock> expiresAt;
        Digest digest;
    };

    virtual FetchBlobResponse
    fetchBlob(const FetchBlobRequest &request,
              GrpcClient::RequestStats *requestStats = nullptr);
    virtual FetchResult fetchBlob(
        const std::vector<std::string> &uris,
        const std::vector<std::pair<std::string, std::string>> &qualifiers,
        GrpcClient::RequestStats *requestStats = nullptr);

    virtual std::optional<FetchBlobResponse>
    tryFetchBlob(const FetchBlobRequest &request,
                 GrpcClient::RequestStats *requestStats = nullptr);
    virtual std::optional<FetchResult> tryFetchBlob(
        const std::vector<std::string> &uris,
        const std::vector<std::pair<std::string, std::string>> &qualifiers,
        GrpcClient::RequestStats *requestStats = nullptr);

    virtual FetchDirectoryResponse
    fetchDirectory(const FetchDirectoryRequest &request,
                   GrpcClient::RequestStats *requestStats = nullptr);
    virtual FetchResult fetchDirectory(
        const std::vector<std::string> &uris,
        const std::vector<std::pair<std::string, std::string>> &qualifiers,
        GrpcClient::RequestStats *requestStats = nullptr);

    virtual PushBlobResponse
    pushBlob(const PushBlobRequest &request,
             GrpcClient::RequestStats *requestStats = nullptr);
    virtual void pushBlob(
        const std::vector<std::string> &uris,
        const std::vector<std::pair<std::string, std::string>> &qualifiers,
        const Digest &digest, const std::vector<Digest> &referencedBlobs = {},
        const std::vector<Digest> &referencedDirectories = {},
        GrpcClient::RequestStats *requestStats = nullptr);

    virtual PushDirectoryResponse
    pushDirectory(const PushDirectoryRequest &request,
                  GrpcClient::RequestStats *requestStats = nullptr);
    virtual void pushDirectory(
        const std::vector<std::string> &uris,
        const std::vector<std::pair<std::string, std::string>> &qualifiers,
        const Digest &rootDirDigest,
        const std::vector<Digest> &referencedBlobs = {},
        const std::vector<Digest> &referencedDirectories = {},
        GrpcClient::RequestStats *requestStats = nullptr);

    std::shared_ptr<GrpcClient> getGrpcClient() const;

  private:
    std::shared_ptr<GrpcClient> d_grpcClient;
    std::shared_ptr<Fetch::StubInterface> d_fetchClient;
    std::shared_ptr<Push::StubInterface> d_pushClient;
};

} // namespace buildboxcommon

#endif // INCLUDED_BUILDBOXCOMMON_ASSETCLIENT_H
