#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/assessors/include/statistics.h>

#include <maps/wikimap/mapspro/services/mrc/libs/toloka_client/include/yandex/maps/mrc/toloka_client/client.h>

#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/csv/include/output_stream.h>
#include <maps/libs/shellcmd/include/yandex/maps/shell_cmd.h>
#include <maps/libs/common/include/file_utils.h>

#include <util/folder/tempdir.h>

#include <map>
#include <chrono>
#include <vector>
#include <fstream>

using namespace maps;
using namespace maps::mrc::toloka::io;
using namespace maps::wiki::autocart::pipeline;

namespace {

static const std::string SCHEMA = "https://";
static const size_t MAX_REQUEST_ATTEMPTS = 5;
static const std::chrono::seconds TIMEOUT(10);
static const std::chrono::seconds RETRY_INITIAL_TIMEOUT(1);
static const double RETRY_TIMEOUT_BACKOFF = 2.;

static const std::string FIELD_ID = "id";
static const std::string FIELD_LOGIN = "login";
static const std::string FIELD_COUNT = "count";
static const std::string FIELD_QUALITY = "quality";

static const std::string UNKNOWN_COMPANY = "unknown";


std::string getCSVPath(const TTempDir& tmpDir, const std::string& company) {
    return common::joinPath(tmpDir.Name().data(), company + ".csv");
}

std::string toPercentage(double quality) {
    REQUIRE(0. <= quality && quality <= 1., "Quality should be in [0, 1]");
    std::stringstream ss;
    ss << std::fixed << std::setprecision(2) << quality * 100 << "%";
    return ss.str();
}

} // namespace

int main(int argc, const char** argv)
try {
    maps::cmdline::Parser parser("Calculate assessors statistics");

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

    maps::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");

    maps::cmdline::Option<std::string> tolokaHost = parser.string("toloka_host")
        .required()
        .help("Toloka host. Example: toloka.yandex.ru (without http:// !)");

    maps::cmdline::Option<std::string> tolokaOAuthToken = parser.string("toloka_token")
        .required()
        .help("Token for Toloka");

    maps::cmdline::Option<std::string> projectId = parser.string("project_id")
        .required()
        .help("Toloka project id");

    maps::cmdline::Option<std::string> startDate = parser.string("start_date")
        .required()
        .help("Count tasks which was accepted not earlier than start date");

    maps::cmdline::Option<std::string> endDate = parser.string("end_date")
        .required()
        .help("Count tasks which was accepted not later than end date");

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

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

    REQUIRE(isCorrectDate(startDate) && isCorrectDate(endDate),
            "Invalid date format. Correct example: 2019-10-19");
    REQUIRE(startDate <= endDate, "Invalid date range");

    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() << "Creating Toloka client";
    TolokaClient tolokaClient(tolokaHost, tolokaOAuthToken);
    tolokaClient.setSchema(SCHEMA)
        .setMaxRequestAttempts(MAX_REQUEST_ATTEMPTS)
        .setTimeout(TIMEOUT)
        .setRetryInitialTimeout(RETRY_INITIAL_TIMEOUT)
        .setRetryTimeoutBackoff(RETRY_TIMEOUT_BACKOFF);

    INFO() << "Calculating assessors statistics for project: " << projectId;
    std::unordered_map<std::string, AssessorStatistics> assessorIdToStatistics
        = getAssessorIdToStatistics(
        tolokaClient, projectId,
        beginOfDay(startDate), endOfDay(endDate)
    );

    INFO() << "Splitting statistics by company";
    std::map<std::string, std::map<Assessor, AssessorStatistics>> companyToStatistics;
    std::map<std::string, AssessorStatistics> unknownAssessorIdToStatistics;
    for (const auto& [assessorId, statistics] : assessorIdToStatistics) {
        auto assessorIt = assessorIdToAssessor.find(assessorId);
        if (assessorIt != assessorIdToAssessor.end()) {
            const Assessor& assessor = assessorIt->second;
            companyToStatistics[assessor.company][assessor] = statistics;
        } else {
            unknownAssessorIdToStatistics[assessorId] = statistics;
        }
    }

    INFO() << "Dumping statistics into csv files";
    std::vector<std::string> csvPaths;
    TTempDir tmpDir;
    for (const auto& [company, assessorToStatistics] : companyToStatistics) {
        std::string csvPath = getCSVPath(tmpDir, company);
        std::ofstream ofs(csvPath);
        REQUIRE(ofs.is_open(), "Failed to create file: " + csvPath);
        csv::OutputStream csvWriter(ofs, csv::COMMA);
        csvWriter << FIELD_ID << FIELD_LOGIN << FIELD_COUNT << FIELD_QUALITY << csv::endl;
        for (const auto& [assessor, statistics] : assessorToStatistics) {
            csvWriter << assessor.id
                      << assessor.login
                      << statistics.completedTasksCount
                      << toPercentage(statistics.quality)
                      << csv::endl;
        }
        ofs.close();
        csvPaths.push_back(csvPath);
    }
    if (!unknownAssessorIdToStatistics.empty()) {
        std::string csvPath = getCSVPath(tmpDir, UNKNOWN_COMPANY);
        std::ofstream ofs(csvPath);
        REQUIRE(ofs.is_open(), "Failed to create file: " + csvPath);
        csv::OutputStream csvWriter(ofs, csv::COMMA);
        csvWriter << FIELD_ID << FIELD_COUNT << FIELD_QUALITY << csv::endl;
        for (const auto& [assessorId, statistics] : unknownAssessorIdToStatistics) {
            csvWriter << assessorId
                      << statistics.completedTasksCount
                      << toPercentage(statistics.quality)
                      << csv::endl;
        }
        ofs.close();
        csvPaths.push_back(csvPath);
    }

    if (!csvPaths.empty()) {
        INFO() << "Archiving all reports: " << outputPath;
        zipFiles(outputPath, csvPaths);
    } else {
        INFO() << "There are no completed tasks during this period";
    }

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