#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/vault_boy/include/secrets.h>
#include <maps/libs/nirvana/include/client.h>
#include <maps/libs/json/include/value.h>
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/libs/log8/include/log8.h>

#include <util/system/datetime.h>
#include <util/random/random.h>
#include <library/cpp/string_utils/base64/base64.h>

namespace {

/**
 * @see
 * https://yav.yandex-team.ru/secret/sec-01cp2k8gbhycereqjrqg2va311/explore/versions
 */
const std::string CONFIG_SECRET = "sec-01cp2k8gbhycereqjrqg2va311";

/**
 * @see
 * https://a.yandex-team.ru/arc/trunk/arcadia/maps/wikimap/mapspro/services/mrc/libs/config/cfg/t-config.production.xml
 */
const std::string CONFIG_PATH = "../../../libs/config/cfg/t-config.production.xml";

/**
 * @see
 * https://nirvana.yandex-team.ru/process/eb9fddab-9d10-44dd-8f2c-6815afc4f3b4
 */
const std::string WORKFLOW = "eb9fddab-9d10-44dd-8f2c-6815afc4f3b4";

const std::string APP_NAME = "collect_house_numbers_panoramas";

std::string temporaryString(const std::string& prefix = APP_NAME) {
    return prefix + ToString(MicroSeconds()).c_str() + "-" + ToString(RandomNumber<ui64>()).c_str();
}

std::string parseJson(const std::string &inputJsonPath, maps::mds::Mds &mds) {
    std::ifstream ifs(inputJsonPath);
    REQUIRE(ifs.is_open(), "Unable to open input json file");

    std::ostringstream toNirvanaJson;
    toNirvanaJson << "[";
    for (bool first = true; !ifs.eof(); first = false) {
        std::string line; std::getline(ifs, line);
        if (line.empty())
            continue;
        maps::json::Value jsonItem = maps::json::Value::fromString(line);
        std::string encimageBase64Str = jsonItem["image"].as<std::string>();
        std::string encimageStr(Base64DecodeBufSize(encimageBase64Str.length()), '\0');
        size_t encimageSize = Base64Decode(encimageStr.data(), encimageBase64Str.begin(), encimageBase64Str.end());
        encimageStr.resize(encimageSize);

        maps::mds::PostResponse resp = mds.post(temporaryString(), encimageStr);
        std::string url = mds.makeReadUrl(resp.key());
        INFO() << url;
        if (!first)
            toNirvanaJson << ",";
        toNirvanaJson << "{\"inputValues\":{\"source\":\"" << url << "\"}}";
    }
    toNirvanaJson << "]";
    maps::mds::PostResponse resp = mds.post(temporaryString(), toNirvanaJson.str());
    return mds.makeReadUrl(resp.key());
}

maps::nirvana::Operation findOperationByName(maps::nirvana::Workflow& work, const std::string& name)
{
    const maps::nirvana::Operations &operations = work.operations();
    for (size_t i = 0; i < operations.size(); i++) {
        if (operations[i].name() == name)
            return operations[i];
    }
    throw::maps::RuntimeError() << "Operation "
            << name << " does not exists in Nirvana workflow "
            << work.globalId();
}

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

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

} // anonymous namespace

int main(int argc, const char** argv) try {
    maps::cmdline::Parser parser("Tool to collect house numbers from json with panoramas images");
    maps::cmdline::Option<std::string> inputPath
        = parser.string("input").required().help("Input json file with panoramas");
    maps::cmdline::Option<std::string> outputPath
        = parser.string("output").required().help("Output YT table with house numbers");
    maps::cmdline::Option<std::string> nirvanaToken
        = parser.string("nirvana-token").required().help("OAuth token for nirvana client");
    parser.parse(argc, const_cast<char**>(argv));
    maps::mrc::common::Config cfg = maps::mrc::common::Config(
        maps::vault_boy::loadContextWithYaVault(CONFIG_SECRET), CONFIG_PATH);
    maps::mds::Mds publicMds = cfg.makePublicMdsClient();

    std::string inputUrl = parseJson(inputPath, publicMds);
    INFO() << inputUrl;
    runWorkflow(nirvanaToken, WORKFLOW, inputUrl, outputPath);
    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    INFO() << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    INFO() << e.what();
    return EXIT_FAILURE;
}
