#include <processargs.h>

#include <buildboxcommon_commandlinetypes.h>
#include <buildboxcommon_connectionoptions_commandline.h>
#include <buildboxcommon_digestgenerator.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_logging_commandline.h>

#include <cstdlib>
#include <cstring>
#include <thread>

namespace casupload_oci {

using ArgumentSpec = buildboxcommon::CommandLineTypes::ArgumentSpec;
using DataType = buildboxcommon::CommandLineTypes::DataType;
using TypeInfo = buildboxcommon::CommandLineTypes::TypeInfo;
using DefaultValue = buildboxcommon::CommandLineTypes::DefaultValue;

const std::string usageText =
    "Downloads OCI/Docker container images from registries and uploads them "
    "to CAS.\n"
    "\n"
    "The tool fetches container image layers from the specified OCI "
    "registry,\n"
    "merges them into a directory tree, and uploads the result to the "
    "Content\n"
    "Addressable Storage (CAS) service.\n"
    "\n"
    "Example usage: casupload-oci --remote=http://localhost:50051 "
    "docker.io/alpine:latest\n";

ProcessedArgs processArgs(int argc, char *argv[])
{
    ProcessedArgs args = {};

    auto connectionOptionsCommandLine =
        buildboxcommon::ConnectionOptionsCommandLine("", "");
    auto casConnectionOptionsCommandLine =
        buildboxcommon::ConnectionOptionsCommandLine("CAS", "cas-");
    auto raConnectionOptionsCommandLine =
        buildboxcommon::ConnectionOptionsCommandLine("Asset", "ra-");

    std::vector<buildboxcommon::CommandLineTypes::ArgumentSpec> spec;

    const auto &connectionOptionsSpec = connectionOptionsCommandLine.spec();
    spec.insert(spec.end(), connectionOptionsSpec.cbegin(),
                connectionOptionsSpec.cend());

    const auto &casConnectionOptionsSpec =
        casConnectionOptionsCommandLine.spec();
    spec.insert(spec.end(), casConnectionOptionsSpec.cbegin(),
                casConnectionOptionsSpec.cend());

    const auto &raConnectionOptionsSpec =
        raConnectionOptionsCommandLine.spec();
    spec.insert(spec.end(), raConnectionOptionsSpec.cbegin(),
                raConnectionOptionsSpec.cend());

    spec.emplace_back("output-digest-file",
                      "Write output root digest to the file in the form "
                      "\"<HASH>/<SIZE_BYTES>\"",
                      TypeInfo(DataType::COMMANDLINE_DT_STRING),
                      ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    spec.emplace_back("output-tree-digest-file",
                      "Write output tree digest to the file in the form "
                      "\"<HASH>/<SIZE_BYTES>\"",
                      TypeInfo(DataType::COMMANDLINE_DT_STRING),
                      ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    spec.emplace_back(
        "oci-auth-token-path",
        "Path to file containing Bearer token for OCI registry authentication",
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);

    spec.emplace_back(
        "digest-function",
        "Set a custom digest function. Default: " +
            buildboxcommon::DigestFunction_Value_Name(
                BUILDBOXCOMMON_DIGEST_FUNCTION_VALUE) +
            "\nSupported functions: " +
            buildboxcommon::DigestGenerator::supportedDigestFunctionsList(),
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);

    spec.emplace_back("num-parallel-layer-threads",
                      "Set the number of parallel threads for OCI layer "
                      "processing. Default: min(hardware-concurrency, 8)",
                      TypeInfo(DataType::COMMANDLINE_DT_INT),
                      ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    auto loggingSpec = buildboxcommon::loggingCommandLineSpec();
    spec.insert(spec.end(), loggingSpec.cbegin(), loggingSpec.cend());

    std::vector<std::string> ociUriVector;
    spec.emplace_back("", "", TypeInfo(&ociUriVector),
                      ArgumentSpec::O_REQUIRED,
                      ArgumentSpec::C_WITH_REST_OF_ARGS);

    auto cl = buildboxcommon::CommandLine(spec, usageText);

    if (!cl.parse(argc, argv)) {
        cl.usage();
        return args;
    }

    if (cl.exists("help") || cl.exists("version")) {
        args.d_processed = true;
        return args;
    }

    if (!buildboxcommon::ConnectionOptionsCommandLine::configureChannel(
            cl, "", &args.d_casConnectionOptions)) {
        return args;
    }
    args.d_assetConnectionOptions = args.d_casConnectionOptions;

    if (!buildboxcommon::ConnectionOptionsCommandLine::updateChannelOptions(
            cl, "cas-", &args.d_casConnectionOptions)) {
        return args;
    }

    if (!buildboxcommon::ConnectionOptionsCommandLine::updateChannelOptions(
            cl, "ra-", &args.d_assetConnectionOptions)) {
        return args;
    }

    if (!buildboxcommon::parseLoggingOptions(cl, args.d_logLevel)) {
        return args;
    }

    args.d_outputRootDigestFile = cl.getString("output-digest-file", "");
    args.d_outputTreeDigestFile = cl.getString("output-tree-digest-file", "");
    args.d_authTokenPath = cl.getString("oci-auth-token-path", "");

    args.d_digestFunctionValue =
        cl.exists("digest-function")
            ? buildboxcommon::DigestGenerator::stringToDigestFunction(
                  cl.getString("digest-function"))
            : static_cast<buildboxcommon::DigestFunction_Value>(
                  BUILDBOXCOMMON_DIGEST_FUNCTION_VALUE);

    args.d_numParallelLayerThreads = cl.getInt(
        "num-parallel-layer-threads", std::thread::hardware_concurrency());

    if (ociUriVector.empty()) {
        BUILDBOX_LOG_ERROR("OCI URI is required");
        return args;
    }

    if (ociUriVector.size() > 1) {
        BUILDBOX_LOG_ERROR("Only one OCI URI is supported");
        return args;
    }
    args.d_ociUri = ociUriVector[0];

    args.d_valid = true;

    return args;
}

} // namespace casupload_oci
