#include <maps/wikimap/mapspro/services/tasks_sprav/src/toloka_downloader/lib/worker.h>

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

#include <maps/libs/pgpool3utils/include/yandex/maps/pgpool3utils/pg_advisory_mutex.h>
#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/default_config.h>
#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/pg_advisory_lock_ids.h>
#include <maps/wikimap/mapspro/libs/tasks/include/runner.h>
#include <maps/wikimap/mapspro/libs/tasks/include/yandex/maps/wiki/tasks/status_writer.h>

#include <filesystem>
#include <optional>

namespace fs = std::filesystem;

namespace maps::wiki::toloka_downloader {
namespace {

const std::string DEFAULT_TASK_TYPE(toString(TaskType::Entrance));

template <typename Option>
std::optional<std::string> makeStatusFilename(
    const Option& statusDir, const std::string& taskType)
{
    std::optional<std::string> statusFilename;
    if (statusDir.defined()) {
        fs::path filepath(static_cast<std::string>(statusDir));
        filepath /= ("wiki-toloka-" + taskType + "-downloader.status");
        statusFilename = filepath;
    }
    return statusFilename;
}

common::AdvisoryLockIds toLockId(TaskType taskType)
{
    switch (taskType) {
        case TaskType::Entrance:
            return common::AdvisoryLockIds::TOLOKA_ENTRANCE_DOWNLOADER;
    }
}

int runWorker(
    const common::ExtendedXmlDoc& config,
    const std::string& tvmAlias,
    const std::optional<std::string>& statusFilename,
    const std::string& taskTypeStr,
    const std::string& ytTable,
    const RunOptions& runOptions)
{
    const auto taskType = enum_io::fromString<TaskType>(taskTypeStr);
    Worker worker(config, tvmAlias, taskType);

    pgp3utils::PgAdvisoryXactMutex dbLocker(
        worker.database().socialPool(), static_cast<int64_t>(toLockId(taskType)));
    if (!dbLocker.try_lock()) {
        INFO() << "Database is already locked. Downloader " << taskType << " interrupted.";
        return EXIT_SUCCESS;
    }

    tasks::StatusWriter statusWriter(statusFilename);
    try {
        INFO() << "Downloading " << taskType;
        auto stats = worker.run(ytTable, runOptions);
        if (stats.duplicated) {
            WARN() << "Found duplicates: " << stats.duplicated;
            statusWriter.warn("duplicates: " + std::to_string(stats.duplicated));
        }
        REQUIRE(!stats.failed, "failed rows: " << stats.failed);
        INFO() << "Downloading " << taskType << " : done";
        statusWriter.flush();
        return EXIT_SUCCESS;
    } catch (const std::exception& ex) {
        ERROR() << "Failed task " << taskType << " " << ex.what();
        statusWriter.err(ex.what());
        statusWriter.flush();
    }
    return EXIT_FAILURE;
}

} // namespace
} // namespace maps::wiki::toloka_downloader

int main(int argc, char** argv) try {
    using namespace maps::wiki;

    maps::cmdline::Parser argsParser;
    auto config = argsParser.file("config")
        .help("Path to config.xml with database connection settings.");
    auto syslogTag = argsParser.string("syslog-tag")
        .help("Redirect log output to syslog with given tag");
    auto statusDir = argsParser.dir("status-dir")
        .help("Path to status dir");
    auto tvmAlias = argsParser.string("tvm-alias")
        .help("TVM alias to access to MRC");
    auto taskType = argsParser.string("task-type")
        .defaultValue(toloka_downloader::DEFAULT_TASK_TYPE)
        .help("Type of supported toloka tasks");
    auto ytTable = argsParser.string("yt-table")
        .help("Source YT table with toloka result tasks");
    auto updateData = argsParser.flag("update-data")
        .help("Update assignment data for already processed tasks");

    argsParser.parse(argc, argv);
    if (syslogTag.defined()) {
        maps::log8::setBackend(maps::log8::toSyslog(syslogTag));
    }

    if (!tvmAlias.defined() || !ytTable.defined()) {
        argsParser.printUsageAndExit(EXIT_FAILURE);
    }

    auto configPtr = config.defined()
        ? std::make_unique<maps::wiki::common::ExtendedXmlDoc>(config)
        : maps::wiki::common::loadDefaultConfig();

    toloka_downloader::RunOptions runOptions;
    runOptions.updateAssignmentData = updateData;
    INFO() << "Update assignment data: " << runOptions.updateAssignmentData;

    return toloka_downloader::runWorker(
        *configPtr,
        tvmAlias,
        toloka_downloader::makeStatusFilename(statusDir, taskType),
        taskType,
        ytTable,
        runOptions
    );
} catch (const maps::Exception& e) {
    FATAL() << "Exception caught: " << e;
    return EXIT_FAILURE;
} catch (const std::exception& ex) {
    FATAL() << "Exception caught: " << ex.what();
    return EXIT_FAILURE;
} catch (...) {
    FATAL() << "Unknown exception caught";
    return EXIT_FAILURE;
}
