#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 <maps/libs/common/include/exception.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/log8/include/log8.h>

#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <set>
#include <thread>
#include <vector>

namespace common = maps::wiki::common;
namespace mlog = maps::log8;

using namespace std::chrono_literals;

namespace {

using TaskId = int64_t;
using Callback = std::function<void(pqxx::transaction_base&, const std::string&, TaskId)>;

const std::string MIN_KEEP_TIME = "5 days";

TaskId getLimitTaskId(
    maps::pgpool3::Pool& corePool,
    const std::string& keepTime,
    const std::string& taskType)
{
    auto work = corePool.masterReadOnlyTransaction();
    auto result = work->exec(
        "SELECT MIN(id) FROM service." + taskType + "_task"
        " JOIN service.task USING (id)"
        " WHERE created >= (NOW() - INTERVAL '" + keepTime + "')"
          " AND created < (NOW() - INTERVAL '" + MIN_KEEP_TIME + "')");
    return result[0][0].as<TaskId>({0});
}

std::set<TaskId> getTaskIds(
    maps::pgpool3::Pool& validationPool,
    const std::string& taskType,
    const std::string& tableName,
    TaskId limitTaskId)
{
    auto work = validationPool.masterReadOnlyTransaction();
    auto rows = work->exec(
        "SELECT DISTINCT task_id"
        " FROM " + taskType + "." + tableName +
        " WHERE task_id < " + std::to_string(limitTaskId));
    std::set<TaskId> taskIds;
    for (const auto& row : rows) {
        taskIds.insert(row[0].as<TaskId>());
    }
    return taskIds;
}

void cleanOldResults(
    maps::pgpool3::Pool& corePool,
    maps::pgpool3::Pool& validationPool,
    const std::string& keepTime,
    const std::string& taskType,
    const std::vector<std::string>& tableNames,
    const Callback& callback = {})
{
    auto limitTaskId = getLimitTaskId(corePool, keepTime, taskType);
    if (!limitTaskId) {
        INFO() << "Cleaner, old " << taskType << " data not found";
        return;
    }

    INFO() << "Cleaner limit " << taskType << " task: " << limitTaskId;
    auto taskIds = getTaskIds(validationPool, taskType, tableNames.back(), limitTaskId);
    if (taskIds.empty()) {
        INFO() << "No " << taskType << " tasks to clean.";
        return;
    }

    INFO() << "Loaded " << taskType << " task ids: " << taskIds.size();
    for (const auto& tableName : tableNames) {
        auto table = taskType + "." + tableName;

        for (auto id : taskIds) {
            auto work = validationPool.masterWriteableTransaction();
            if (callback) {
                callback(*work, table, id);
            }

            auto res = work->exec(
                "DELETE FROM " + table +
                " WHERE task_id = " + std::to_string(id));
            INFO() << res.affected_rows() << " rows deleted from table " << table
                << " for task id: " << id;
            work->commit();

            std::this_thread::sleep_for(1s);
        }
    }
}

void cleanOldResults(
    const common::ExtendedXmlDoc& configDoc,
    const std::string& keepTime)
{
    INFO() << "Cleaner started, keep-time: " << keepTime;
    common::PoolHolder coreDbHolder(configDoc, "core", "grinder");
    common::PoolHolder validationDbHolder(configDoc, "validation", "grinder");

    cleanOldResults(
        coreDbHolder.pool(),
        validationDbHolder.pool(),
        keepTime,
        "validation",
        {"message_view", "task_message", "task_message_stats"}
    );

    cleanOldResults(
        coreDbHolder.pool(),
        validationDbHolder.pool(),
        keepTime,
        "diffalert",
        {"messages"},
        [](pqxx::transaction_base& txn, const std::string& table, TaskId taskId) {
            txn.exec(
                "DELETE FROM diffalert.startrek_issue"
                " WHERE message_id IN ("
                    "SELECT id FROM " + table +
                    " WHERE task_id = " + std::to_string(taskId) + ")");
        }
    );

    INFO() << "Cleaner, OK";
}

} // anonymous namespace

int main(int argc, char* argv[]) try {
    maps::cmdline::Parser parser;
    auto config = parser
        .string("config")
        .help("path to worker configuration");
    auto syslogTag = parser
        .string("syslog-tag")
        .help("redirect log output to syslog with given tag");
    auto keepTime = parser
        .string("keep-time")
        .required()
        .help("clean results older then (example: '2 months')");

    parser.parse(argc, argv);

    if (syslogTag.defined()) {
        mlog::setBackend(mlog::toSyslog(syslogTag));
    }

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

    cleanOldResults(*configDocPtr, keepTime);
    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;
}
