// Copyright 2025 Bloomberg Finance L.P
//
// 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.

#include <buildboxcommon_assetclient.h>
#include <buildboxcommon_casclient.h>
#include <buildboxcommon_digestgenerator.h>
#include <buildboxcommon_exception.h>
#include <buildboxcommon_fileutils.h>
#include <buildboxcommon_grpcclient.h>
#include <buildboxcommon_httpclient.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_ociclient.h>
#include <buildboxcommon_stringutils.h>
#include <buildboxcommon_version.h>

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <processargs.h>
#include <ThreadPool.h>

using namespace buildboxcommon;
using namespace casupload_oci;

constexpr int DEFAULT_MAX_NUM_LAYER_THREADS = 8;

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

        if (args.d_processed) {
            return 0;
        }

        if (!args.d_valid) {
            return 2;
        }

        BUILDBOX_LOG_SET_LEVEL(args.d_logLevel);

        BUILDBOX_LOG_DEBUG("Starting casupload-oci...");

        buildboxcommon::DigestGenerator::init(args.d_digestFunctionValue);

        auto httpClient = std::make_shared<HTTPClient>();

        auto grpcClient = std::make_shared<GrpcClient>();
        grpcClient->init(args.d_casConnectionOptions);
        grpcClient->setToolDetails("casupload-oci", buildboxcommon::VERSION);
        const std::string toolInvocationId =
            buildboxcommon::StringUtils::getUUIDString();
        const std::string correlatedInvocationsId =
            buildboxcommon::StringUtils::getUUIDString();
        grpcClient->setRequestMetadata("", toolInvocationId,
                                       correlatedInvocationsId);
        auto casClient = std::make_shared<CASClient>(grpcClient);

        if (!args.d_casConnectionOptions.d_url.empty()) {
            casClient->init();
        }

        auto assetGrpcClient = std::make_shared<GrpcClient>();
        assetGrpcClient->init(args.d_assetConnectionOptions);
        auto assetClient = std::make_shared<AssetClient>(assetGrpcClient);

        if (!args.d_assetConnectionOptions.d_url.empty()) {
            assetClient->init();
        }

        // Create OciClient with optional auth token path
        std::optional<std::string> authTokenPath =
            args.d_authTokenPath.empty()
                ? std::nullopt
                : std::make_optional(args.d_authTokenPath);

        OciClient ociClient(httpClient, casClient, assetClient, std::nullopt,
                            authTokenPath);

        // Create ThreadPool with user-specified threads limited by default max

        int d_numLayerThreads = std::min(args.d_numParallelLayerThreads,
                                         DEFAULT_MAX_NUM_LAYER_THREADS);

        std::unique_ptr<ThreadPool> layerThreadPool =
            (d_numLayerThreads > 1)
                ? std::make_unique<ThreadPool>(d_numLayerThreads, "oci")
                : nullptr;

        BUILDBOX_LOG_DEBUG("Fetching OCI image: " << args.d_ociUri);
        BUILDBOX_LOG_DEBUG(
            "CAS server: " << args.d_casConnectionOptions.d_url);
        BUILDBOX_LOG_DEBUG(
            "Asset server: " << args.d_assetConnectionOptions.d_url);

        BUILDBOX_LOG_DEBUG("Getting image digests...");
        auto digests =
            ociClient.getImageDigests(args.d_ociUri, layerThreadPool.get());
        Digest treeDigest = digests.first;
        Digest rootDigest = digests.second;

        if (!args.d_outputTreeDigestFile.empty()) {
            FileUtils::writeDigestToFile(treeDigest,
                                         args.d_outputTreeDigestFile);
            BUILDBOX_LOG_DEBUG(
                "Tree digest written to: " << args.d_outputTreeDigestFile);
        }

        if (!args.d_outputRootDigestFile.empty()) {
            FileUtils::writeDigestToFile(rootDigest,
                                         args.d_outputRootDigestFile);
            BUILDBOX_LOG_DEBUG(
                "Root digest written to: " << args.d_outputRootDigestFile);
        }

        BUILDBOX_LOG_INFO(
            "Successfully processed OCI image: " << args.d_ociUri);
        BUILDBOX_LOG_INFO("Tree digest: " << treeDigest.hash() << "/"
                                          << treeDigest.size_bytes());
        BUILDBOX_LOG_INFO("Root digest: " << rootDigest.hash() << "/"
                                          << rootDigest.size_bytes());

        return 0;
    }
    catch (const std::exception &e) {
        BUILDBOX_LOG_ERROR("Error: " << e.what());
        return 1;
    }
}
