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

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/assessors/include/utils.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/assessors/include/assessor.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/assessors_results.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/publication_results.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/yt_storage.h>

#include <maps/libs/csv/include/output_stream.h>

#include <util/string/split.h>

#include <fstream>
#include <iostream>

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

namespace {

static const std::string BLD_ID = "bld_id";
static const std::string LOGIN = "login";

std::set<int64_t> parseBuildingIds(const std::string& bldIdsList) {
    std::set<int64_t> bldIds;
    for (const std::string& bldId
            : StringSplitter(bldIdsList).Split(',').ToList<std::string>())
    {
        bldIds.insert(std::stoll(bldId));
    }
    return bldIds;
}

std::vector<PublicationResult> loadPublicationResults(
    NYT::IClientPtr client,
    const YTStorageClient& storage,
    const std::set<int64_t> bldIds)
{
    NYT::TTempTable cloneTable(client);
    storage.cloneResultsTable<PublicationResult>(client, cloneTable.Name());

    std::vector<PublicationResult> results;
    NYT::TTableReaderPtr<NYT::TNode> reader
        = client->CreateTableReader<NYT::TNode>(cloneTable.Name());
    for (; reader->IsValid(); reader->Next()) {
        const NYT::TNode& node = reader->GetRow();
        PublicationResult result = PublicationResult::fromYTNode(node);

        if (result.bld.hasId()) {
            if (bldIds.count(result.bld.getId())) {
                results.push_back(result);
            }
        }
    }

    return results;
}

std::map<uint64_t, AssessorsResult> loadAssessorsResults(
    NYT::IClientPtr client,
    const YTStorageClient& storage,
    const std::set<uint64_t> resultIds)
{
    NYT::TTempTable cloneTable(client);
    storage.cloneResultsTable<AssessorsResult>(client, cloneTable.Name());

    std::map<uint64_t, AssessorsResult> resultIdToAssessorsResult;
    NYT::TTableReaderPtr<NYT::TNode> reader
        = client->CreateTableReader<NYT::TNode>(cloneTable.Name());
    for (; reader->IsValid(); reader->Next()) {
        const NYT::TNode& node = reader->GetRow();
        AssessorsResult result = AssessorsResult::fromYTNode(node);

        if (resultIds.count(result.id)) {
            resultIdToAssessorsResult.insert({result.id, result});
        }
    }

    return resultIdToAssessorsResult;
}


} // namespace

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

    cmdline::Parser parser("Extract users that approve buildings");

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

    cmdline::Option<std::string> bldIdsList = parser.string("bld_ids")
        .required()
        .help("List of building ids separated by comma");

    cmdline::Option<std::string> wikiOAuthToken = parser.string("wiki_token")
        .required()
        .help("Token for wiki. See https://auth.yandex-team.ru");

    cmdline::Option<std::string> wikiTableName = parser.string("wiki_table")
        .required()
        .help("Name of wiki table. Example:\n"
              " * URL - https://wiki.yandex-team.ru/users/dolotov-e/Arkadija/\n"
              " * name - users/dolotov-e/Arkadija");

    cmdline::Option<std::string> outputPath = parser.string("output")
        .required()
        .help("Path to output csv file");

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

    INFO() << "Loading assessors list: " << wikiTableName;
    std::vector<Assessor> assessors
        = loadAssessorsFromWikiTable(wikiTableName, wikiOAuthToken);
    INFO() << "Loaded " << assessors.size() << " assessors";
    std::map<std::string, Assessor> assessorIdToAssessor;
    for (const Assessor& assessor : assessors) {
        assessorIdToAssessor[assessor.id] = assessor;
    }

    INFO() << "Connecting to yt::hahn";
    NYT::IClientPtr ytClient = NYT::CreateClient("hahn");

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

    INFO() << "Connecting to YT storage: " << config.storagePath();
    YTStorageClient ytStorageClient(ytClient, config.storagePath());

    INFO() << "Loading building ids";
    std::set<int64_t> bldIds = parseBuildingIds(bldIdsList);
    INFO() << "Loaded " << bldIds.size() << " ids";

    INFO() << "Loading publication results";
    std::vector<PublicationResult> publicationResults
        = loadPublicationResults(ytClient, ytStorageClient, bldIds);
    INFO() << "Loaded " << publicationResults.size() << " results";

    std::set<uint64_t> resultIds;
    for (const PublicationResult& result : publicationResults) {
        resultIds.insert(result.id);
    }

    INFO() << "Loading assessors results";
    std::map<uint64_t, AssessorsResult> resultIdToAssessorsResult
        = loadAssessorsResults(ytClient, ytStorageClient, resultIds);
    INFO() << "Loaded " << resultIdToAssessorsResult.size() << " results";

    std::ofstream ofs(outputPath);
    REQUIRE(ofs.is_open(), "Failed to create file: " + outputPath);
    csv::OutputStream csvWriter(ofs, csv::COMMA);
    csvWriter << BLD_ID << LOGIN << csv::endl;

    for (const PublicationResult& publicationResult : publicationResults) {
        int64_t bldId = publicationResult.bld.getId();
        std::string login = "unknown";

        auto assessorsResultIt = resultIdToAssessorsResult.find(publicationResult.id);
        if (assessorsResultIt != resultIdToAssessorsResult.end()) {
            auto assessorIt = assessorIdToAssessor.find(assessorsResultIt->second.assessorId);
            if (assessorIt != assessorIdToAssessor.end()) {
                login = assessorIt->second.login;
            }
        }

        csvWriter << bldId << login << csv::endl;
    }

    ofs.close();

    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;
}
