#include "tools.h"

#include <maps/libs/json/include/builder.h>
#include <maps/libs/nirvana/include/client.h>
#include <util/generic/guid.h>
#include <maps/libs/http/include/http.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/retry.h>

#include <fstream>

namespace maps::mrc::tools {
namespace {

const std::string APP_NAME = "collect_house_numbers";

auto findOperationByName(nirvana::Workflow& work, const std::string& name)
{
    auto it = std::find_if(work.operations().begin(), work.operations().end(),
        [&](const auto& op) { return op.name() == name; });
    REQUIRE(it != work.operations().end(), "Operation "
            << name << " does not exists in Nirvana workflow "
            << work.globalId());
    return *it;
}

auto download(const http::URL& url)
{
    static http::Client httpClient;
    http::Request request{httpClient, http::GET, url};
    auto response = request.perform();
    if (response.status() == 200) {
        return response.readBody();
    } else {
        throw Exception{} << "Unexpected status: " << response.status()
                          << " from: " << url;
    }
}

template <typename Functor>
auto retry(Functor&& f)
{
    return common::retryOnException<std::exception>(
        common::RetryPolicy()
            .setInitialTimeout(std::chrono::seconds(1))
            .setMaxAttempts(5)
            .setTimeoutBackoff(2),
        std::forward<Functor>(f));
}

} // anonymous namespace

URLs readUrls(const std::string& inputPath)
{
    URLs result;
    INFO() << inputPath;
    std::ifstream file(inputPath, std::ios_base::in);
    for (std::string line; std::getline(file, line);) {
        result.insert(std::move(line));
    }
    INFO() << "Loaded: " << result.size() << " URLs";
    return result;
}

URL postToMds(const std::string& data, mds::Mds& publicMds)
{
    std::ostringstream path;
    path << APP_NAME << "/" << CreateGuidAsString();
    auto resp = publicMds.post(path.str(), data);
    return publicMds.makeReadUrl(resp.key());
}

URLs repostToMds(const URLs& urls, mds::Mds& publicMds)
{
    URLs result;
    for (const auto& url : urls) {
        auto img = retry([&] { return download(url); });
        result.insert(postToMds(img, publicMds));
        INFO() << "Posted: " << url;
    }
    return result;
}

std::string toInputJson(const URLs& urls)
{
    json::Builder builder;
    builder << [&](json::ArrayBuilder b) {
        for (const auto& url : urls) {
            b << [&](json::ObjectBuilder b) {
                b["inputValues"]
                    << [&](json::ObjectBuilder b) { b["source"] = url; };
            };
        }
    };
    return builder.str();
}

void runWorkflow(const std::string& nirvanaToken,
    const std::string& workflow,
    const std::string& inputUrl,
    const std::string& outputPath)
{
    static const auto INPUT_OP = "sh wget (one file)";
    static const auto OUTPUT_OP = "upload_house_numbers_to_yt";

    INFO() << "Connecting to Nirvana: " << nirvanaToken;
    auto client = nirvana::Client{
        APP_NAME, std::make_unique<nirvana::OAuth>(nirvanaToken)};
    INFO() << "Getting workflow: " << workflow;
    auto original = client.getWorkflow(workflow);
    INFO() << "Cloning workflow: " << original.globalId();
    auto clone = client.cloneWorkflow(original);
    INFO() << "Setting parameters";
    client.setOperationParameters(clone, findOperationByName(clone, INPUT_OP),
        {{"url", json::Value::fromString('"' + inputUrl + '"')}});
    client.setOperationParameters(clone,
        findOperationByName(clone, OUTPUT_OP),
        {{"output", json::Value::fromString('"' + outputPath + '"')}});
    INFO() << "Starting workflow: " << clone.globalId();
    client.startWorkflow(clone);
    INFO() << "Workflow URL: " << clone.userInterfaceUrl().toString();
}

} // maps::mrc::tools
