#include <maps/wikimap/mapspro/services/mrc/libs/common/include/ecstatic_client.h>

#include <maps/libs/common/include/exception.h>
#include <maps/libs/enum_io/include/enum_io.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/process/include/process.h>

#include <boost/filesystem/operations.hpp>

namespace maps::mrc::common {

using namespace std::string_view_literals;

namespace {

std::string formatDatasetNameVersion(const DatasetVersion& datasetVersion)
{
    return datasetVersion.dataset() + "=" + datasetVersion.version();
}

DatasetVersion parseDatasetVersionString(const std::string& datasetVersion)
{
    auto eqPos = datasetVersion.find('=');
    REQUIRE(eqPos != std::string::npos, "Invalid format: " << datasetVersion);

    std::string dataset = datasetVersion.substr(0, eqPos);
    std::string version = datasetVersion.substr(eqPos + 1);
    return DatasetVersion(std::move(dataset), std::move(version));
}

} // namespace


constexpr enum_io::Representations<EcstaticBranch> ECSTATIC_BRANCH_REPRESENTATIONS{
    {EcstaticBranch::Testing, "testing"sv},
    {EcstaticBranch::Stable, "stable"sv},
};

DEFINE_ENUM_IO(EcstaticBranch, ECSTATIC_BRANCH_REPRESENTATIONS);

EcstaticClient::EcstaticClient(
    const std::string& bin,
    const std::optional<maps::common::Environment>& environmentName)
    : bin_(bin)
    , environmentName_(environmentName)
{
    REQUIRE(!bin_.empty(), "Ecstatic bin path should not be empty");
    REQUIRE(
        !environmentName_.has_value() ||
            (environmentName_.value() != maps::common::Environment::Other),
        "Wrong ecstatic environment value" << environmentName_.value());
}

process::Command
EcstaticClient::makeEcstaticCommand(std::vector<std::string> args) const
{
    std::vector<std::string> cmd{bin_};
    cmd.reserve(args.size() + 1);
    std::move(args.begin(), args.end(), std::back_inserter(cmd));
    process::Command command(std::move(cmd));
    if (environmentName_.has_value()) {
        command.inEnv({{"ENVIRONMENT_NAME", std::string(toString(environmentName_.value()))}});
    }
    return command;
}

void EcstaticClient::upload(const std::string& localDirectory, const DatasetVersion& datasetVersion) const
{
    REQUIRE(!localDirectory.empty(), "Local directory path should not be empty");
    auto cmdUpload = makeEcstaticCommand({"upload", formatDatasetNameVersion(datasetVersion), localDirectory});
    for (const auto& branch: enum_io::enumerateValues<EcstaticBranch>()) {
        cmdUpload.addArgument("+" + std::string(toString(branch)) + "/hold");
    }
    INFO() << "EcstaticClient: " << cmdUpload;
    process::runAndCheck(cmdUpload);
}

void EcstaticClient::activate(const DatasetVersion& datasetVersion, EcstaticBranch branch) const
{
    auto nameVersion = formatDatasetNameVersion(datasetVersion);
    auto cmd = makeEcstaticCommand({"move", nameVersion, "+" + std::string(toString(branch))});
    INFO() << "EcstaticClient: " << cmd;
    process::runAndCheck(cmd);
}

void EcstaticClient::waitForActivation(
        const DatasetVersion& datasetVersion,
        EcstaticBranch branch,
        std::optional<std::chrono::seconds> timeout) const
{
    auto nameVersion = formatDatasetNameVersion(datasetVersion);
    std::vector<std::string> cmdArgs({"wait"});
    if (timeout.has_value()) {
        cmdArgs.insert(cmdArgs.end(), {"--timeout", std::to_string(timeout.value().count())});
    }
    cmdArgs.insert(cmdArgs.end(), {nameVersion, std::string(toString(branch))});
    auto cmd = makeEcstaticCommand(std::move(cmdArgs));
    INFO() << "EcstaticClient: " << cmd;
    process::runAndCheck(cmd);
}

void EcstaticClient::deactivate(const DatasetVersion& datasetVersion, EcstaticBranch branch) const
{
    auto cmdMove = makeEcstaticCommand({"move", formatDatasetNameVersion(datasetVersion), "--", "-" + std::string(toString(branch))});
    INFO() << "EcstaticClient: " << cmdMove;
    process::runAndCheck(cmdMove);
}

void EcstaticClient::remove(const DatasetVersion& datasetVersion) const
{
    auto cmdRemove = makeEcstaticCommand({"remove", formatDatasetNameVersion(datasetVersion)});
    INFO() << "EcstaticClient: " << cmdRemove;
    process::runAndCheck(cmdRemove);
}

std::vector<DatasetVersion> EcstaticClient::versions(const std::string& dataset, EcstaticBranch branch) const
{
    auto cmd = makeEcstaticCommand({"versions", dataset, std::string(toString(branch))});
    INFO() << "EcstaticClient: " << cmd;
    auto process = maps::process::runInteractive(cmd);
    process.closeInputStream();

    std::vector<DatasetVersion> datasetVersions;
    std::string line;
    while (std::getline(process.outputStream(), line)) {
        if (line.empty()) {
            continue;
        }
        datasetVersions.push_back(parseDatasetVersionString(line));
    }
    process.closeOutputStream();
    auto status = process.syncWait();

    REQUIRE(!status.exitStatus(), "Ecstatic tool finished with error");
    return datasetVersions;
}

} // namespace maps::mrc::common
