#include <maps/wikimap/mapspro/services/tasks_export/src/export_worker/lib/db_log.h>
#include <maps/wikimap/mapspro/services/tasks_export/src/export_worker/lib/export_files.h>
#include <maps/wikimap/mapspro/services/tasks_export/src/export_worker/lib/run.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/tools/grinder/worker/include/api.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/mongo/include/init.h>
#include <yandex/maps/wiki/common/default_config.h>
#include <yandex/maps/wiki/common/extended_xml_doc.h>
#include <yandex/maps/wiki/common/pgpool3_helpers.h>
#include <yandex/maps/wiki/common/retry_duration.h>
#include <yandex/maps/wiki/tasks/export.h>
#include <yandex/maps/wiki/tasks/task_logger.h>

#include <memory>
#include <numeric>
#include <thread>
#include <utility>

namespace common = maps::wiki::common;
namespace mlog = maps::log8;
namespace rev = maps::wiki::revision;
namespace tasks = maps::wiki::tasks;
namespace worker = maps::grinder::worker;

namespace {
const std::string TASK_TYPE = "export";
constexpr size_t WORKER_CONCURRENCY = 1;
constexpr std::chrono::hours DEFAULT_RETRY_DURATION{8};
constexpr std::chrono::hours WORKER_PRIORITY_PERIOD{1};
} // namespace

namespace maps::wiki::exporter {

int calcWorkerPriority()
{
    constexpr int NOK_THREADS = std::lcm(256, std::lcm(104, 80));

    auto hardwareThreads = std::thread::hardware_concurrency();
    int priority = NOK_THREADS / std::max(1u, hardwareThreads);

    INFO()
        << "Priority calculator,"
        << " hardware threads: " << hardwareThreads
        << " priority: " << priority;
    return priority;
}

void exportWorker(
    const worker::Task& task,
    const common::ExtendedXmlDoc& configXml)
{
    TaskParams taskParams(task);
    ExportConfig exportCfg(taskParams, configXml);
    exportCfg.detectAndLockNearestExportDatabase();

    INFO() << exportCfg;

    exportCfg.setLogger(std::make_unique<DbLogger>(
        std::make_shared<tasks::TaskPgLogger>(
            exportCfg.mainPool(), taskParams.taskId)));

    exportCfg.logger().logInfo() << "Task started. Grinder task id: " << task.id();

    try {
        auto rootDir = configXml.getAttr<std::string>(tasks::getConfigExportXpath(), "tmp-base-dir");
        auto datasets = runExport(exportCfg, rootDir);
        saveResult(exportCfg, exportCfg.mainPool(), datasets);
    } catch (const ExportCanceledException&) {
        exportCfg.logger().logWarn() << "Task canceled";
        throw grinder::worker::TaskCanceledException();
    } catch (const maps::Exception& e) {
        ERROR() << "Export failed: " << e;
        exportCfg.logger().logError() << "Task failed: " << e.what();
        throw;
    } catch (const std::exception& e) {
        ERROR() << "Export failed: " << e.what();
        exportCfg.logger().logError() << "Task failed: " << e.what();
        throw;
    }

    exportCfg.logger().logInfo() << "Task finished";
}

} // namespace maps::wiki::exporter


int main(int argc, char* argv[]) try
{
    maps::cmdline::Parser parser;
    auto workerConfig = parser.string("config")
        .help("path to worker configuration");
    auto syslogTag = parser.string("syslog-tag")
        .help("redirect log output to syslog with given tag");
    auto grinderConfig = parser.string("grinder-config")
        .help("path to grinder configuration file");
    auto geodataPath = parser.string("geodata-path")
        .help("Yandex geodata path"); // /var/cache/geobase/geodata$.bin

    parser.parse(argc, argv);

    common::RetryDurationPolicy::setDefaultDuration(DEFAULT_RETRY_DURATION);
    if (syslogTag.defined()) {
        mlog::setBackend(mlog::toSyslog(syslogTag));
    }

    maps::mongo::init();

    auto configDocPtr = workerConfig.defined()
        ? std::make_unique<common::ExtendedXmlDoc>(workerConfig)
        : common::loadDefaultConfig();

    worker::Options workerOpts;

    if (grinderConfig.defined()) {
        workerOpts.setConfigLocation(grinderConfig);
    }
    workerOpts.setPriorityEvaluator(
        [] { return maps::wiki::exporter::calcWorkerPriority(); },
        WORKER_PRIORITY_PERIOD);

    maps::wiki::exporter::setGeoData(geodataPath);

    workerOpts.on(TASK_TYPE,
            [&](const worker::Task& task) {
                maps::wiki::exporter::exportWorker(task, *configDocPtr);
            })
        .setConcurrencyLevel(WORKER_CONCURRENCY);

    worker::run(std::move(workerOpts));

    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    ERROR() << "Worker failed: " << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    ERROR() << "Worker failed: " << e.what();
    return EXIT_FAILURE;
}
