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

#include <maps/libs/json/include/builder.h>

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/yt_storage.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/detection_results.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/tolokers_results.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/assessors_results.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/base_results.h>

#include <mapreduce/yt/interface/client.h>

#include <vector>
#include <fstream>
#include <sstream>

using namespace maps::wiki::autocart::pipeline;

namespace {

template <typename Result>
void prepareBuildings(
    YTStorageClient& storage,
    const std::string& region,
    uint64_t issueId,
    const std::string& resultsJsonPath)
{
    INFO() << "Loading " << Result::getName() << " results:"
           << " region - " << region << ", issue id - " << issueId;
    std::vector<Result> results;
    storage.getResults(TString(region), issueId, &results);
    INFO() << "Loaded " << results.size() << " results";

    INFO() << "Removing rejected results";
    results.erase(
        std::remove_if(
            results.begin(), results.end(),
            [](const Result& result) {
                return result.state != TolokaState::Yes;
            }
        ),
        results.end()
    );
    REQUIRE(!results.empty(), "Failed to prepare tasks with empty input");
    INFO() << results.size() << " results left";

    INFO() << "Preparing buildings";
    std::ofstream ofs(resultsJsonPath);
    REQUIRE(ofs.is_open(), "Failed to open file: " + resultsJsonPath);
    maps::json::Builder builder(ofs);
    builder << [&](maps::json::ArrayBuilder b) {
        for (const Result& result : results) {
            BaseResult baseResult;
            baseResult.id = result.id;
            baseResult.bld = result.bld;
            b << [&](maps::json::ObjectBuilder b) {
                baseResult.toJson(b);
            };
        }
    };
    ofs.close();
}

} // namespace

int main(int argc, const char** argv)
try {
    NYT::Initialize(argc, argv);

    maps::cmdline::Parser parser("Prepare bulidings for publication");

    maps::cmdline::Option<std::string> ytConfigPath = parser.string("yt_config")
        .required()
        .help("Path to YT config");

    maps::cmdline::Option<std::string> region = parser.string("region")
        .required()
        .help("MPRO region name");

    maps::cmdline::Option<size_t> issueId = parser.size_t("issue_id")
        .required()
        .help("issue id of satellite factory");

    maps::cmdline::Option<bool> useTolokers = parser.flag("use_tolokers")
        .defaultValue(false)
        .help("Use tolokers results");

    maps::cmdline::Option<bool> useAssessors = parser.flag("use_assessors")
        .defaultValue(false)
        .help("Use assessors results");

    maps::cmdline::Option<std::string> resultsJsonPath = parser.string("results")
        .required()
        .help("Path to output file with buildings");

    parser.parse(argc, const_cast<char**>(argv));

    INFO() << "Loading YT config: " << ytConfigPath;
    YTConfig ytConfig(ytConfigPath);

    INFO() << "Create YT client: yt::hahn";
    NYT::IClientPtr client = NYT::CreateClient("hahn");

    INFO() << "Create YT storage client: " << ytConfig.storagePath();
    YTStorageClient storage(client, TString(ytConfig.storagePath()));

    if (useAssessors) {
        prepareBuildings<AssessorsResult>(storage, region, issueId, resultsJsonPath);
    } else if (useTolokers) {
        prepareBuildings<TolokersResult>(storage, region, issueId, resultsJsonPath);
    } else {
        prepareBuildings<DetectionResult>(storage, region, issueId, resultsJsonPath);
    }

    return EXIT_SUCCESS;
}
catch (const maps::Exception& e) {
    INFO() << e;
    return EXIT_FAILURE;
}
catch (const std::exception& e) {
    INFO() << e.what();
    return EXIT_FAILURE;
}
catch (...) {
    INFO() << "Caught unknown exception";
    return EXIT_FAILURE;
}
