/*
 * Copyright 2019 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_BUILDBOXRUN_USERCHROOT
#define INCLUDED_BUILDBOXRUN_USERCHROOT

#include <buildboxcommon_fileutils.h>
#include <buildboxcommon_mergeutil.h>
#include <buildboxcommon_runner.h>
#include <buildboxcommon_runnerutils.h>

#ifdef OCI
#include <buildboxcommon_ociclient.h>
#endif
#include <filesystem>
#include <optional>
#include <unordered_map>

namespace buildboxcommon {
namespace buildboxrun {
namespace bubblewrap {

class BubbleWrapRunner : public Runner {
  public:
    BubbleWrapRunner(const std::string &bwrap_path = "");

    typedef std::vector<
        std::pair<std::filesystem::path, std::filesystem::path>>
        BindPaths;

    /**
     * Execute the given Command in the given input root and return an
     * ActionResult. This will be performed using bubblewrap.
     */
    ActionResult execute(const Command &command, const Digest &inputRootDigest,
                         const Platform &platform) override;

    /**
     * Parse runner-specific arguments.
     * Return true if an argument was handled successfully.
     */
    bool parseArg(std::string_view arg) override;

    /**
     * Print runner-specific usage message.
     */
    void printSpecialUsage() override;

    /**
     * Print runner-specific capabilities.
     */
    void printSpecialCapabilities() override;

    const std::vector<std::string>
    generateCommandLine(const Command &command, const std::string &root_path,
                        const Platform &platform);

    static bool userNamespaceAvailable();

  private:
    std::vector<std::string> d_bindArguments;

    BindPaths d_roBindPaths;
    // Conditionally ro-bind file(s) into chroot by platform properties
    std::unordered_map<std::string, BindPaths> d_roBindPathsByProperties;

    // These capabilities are derived from what docker adds by default,
    // with a couple of the more higher risk ones removed.
    // https://github.com/moby/moby/blob/c74ba95583051d0d49280d242b3b72bc3e54693d/daemon/pkg/oci/caps/defaults.go#L6-L19
    std::unordered_set<std::string> d_supportedCapabilities{
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE",
        "CAP_FSETID",
        "CAP_FOWNER",
        "CAP_MKNOD",
        "CAP_NET_RAW",
        "CAP_NET_BIND_SERVICE",
        "CAP_KILL",
        "CAP_AUDIT_WRITE"};
    std::vector<std::string> d_addedCapabilities;

    std::string d_bubblewrapBinPath;
    std::string d_linux32Path;
    bool d_userNamespaceAvailable;
    bool d_enableTmpOutputs;

#ifdef OCI
    std::optional<std::string> d_ociAuthTokenPath;
    int d_numParallelLayerThreads;
#endif

    void mergeDigests(const Digest &inputDigest, const Digest &chrootDigest,
                      Digest *mergedDigest,
                      const Command_OutputDirectoryFormat
                          &chrootDirectoryFormat = Command::DIRECTORY_ONLY);

    void uploadMissingBlobs(const digest_string_map &data) const;

    // Call `LocalCAS.FetchTree()` for the given Digest.
    void fetchChrootIntoLocalCas(const buildboxcommon::Digest &digest) const;

#ifdef OCI
    std::unique_ptr<OciClient> createOciClient() const;
#endif
};

} // namespace bubblewrap
} // namespace buildboxrun
} // namespace buildboxcommon
#endif
