#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/extended_xml_doc.h>
#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/pg_advisory_lock_ids.h>
#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/pgpool3_helpers.h>
#include <maps/wikimap/mapspro/libs/assessment/include/gateway.h>
#include <maps/libs/pgpool3utils/include/yandex/maps/pgpool3utils/pg_advisory_mutex.h>
#include <maps/libs/chrono/include/days.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/log8/include/log8.h>

#include <memory>

namespace chrono = maps::chrono;
namespace log8 = maps::log8;
namespace common = maps::wiki::common;
namespace assessment = maps::wiki::assessment;

namespace {

constexpr unsigned UNIT_SKIP_OVERDUE_DAYS = 30;

struct Params
{
    chrono::TimePoint oldestUnitSkipDate;
    bool dryRun;

    std::optional<std::string> workerConfigPath;
    std::optional<std::string> syslogTag;
};

Params loadParams(int argc, char* argv[])
{
    const auto oldestDateVal = chrono::formatIsoDate(
        std::chrono::system_clock::now() - chrono::Days(UNIT_SKIP_OVERDUE_DAYS));
    const auto oldestDateDesc =
        "today - " + std::to_string(UNIT_SKIP_OVERDUE_DAYS) + " days";

    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 dryRun = parser.flag("dry-run")
        .defaultValue(false)
        .help("Skip commit to the database");
    auto oldestUnitSkipDate = parser.string("oldest-unit-skip-date")
        .defaultValue(oldestDateVal)
        .help("Older unit-skips will be deleted. Defaults to " + oldestDateDesc);
    parser.parse(argc, argv);

    Params params = {
        .oldestUnitSkipDate = chrono::parseIsoDate(oldestUnitSkipDate),
        .dryRun = dryRun};

    if (config.defined()) {
        params.workerConfigPath = config;
    }
    if (syslogTag.defined()) {
        params.syslogTag = syslogTag;
    }
    return params;
}

common::PoolHolder initSocialPoolHolder(const std::optional<std::string>& path)
{
    static const std::string SOCIAL_DB_ID = "social";
    static const std::string GRINDER_POOL_ID = "grinder";

    const auto configDocPtr = path
        ? std::make_unique<common::ExtendedXmlDoc>(*path)
        : common::loadDefaultConfig();

    return common::PoolHolder(*configDocPtr, SOCIAL_DB_ID, GRINDER_POOL_ID);
}

maps::pgp3utils::PgAdvisoryXactMutex initSocialLocker(maps::pgpool3::Pool& pool)
{
    const auto lockId = static_cast<int64_t>(common::AdvisoryLockIds::SOCIAL_CLEANER);
    return maps::pgp3utils::PgAdvisoryXactMutex(pool, lockId);
}

} // namespace

int main(int argc, char* argv[])
{
    try {
        const auto params = loadParams(argc, argv);

        if (params.syslogTag) {
            log8::setBackend(log8::toSyslog(*params.syslogTag));
        }
        INFO() << "Social cleaner started";

        auto socialPoolHolder = initSocialPoolHolder(params.workerConfigPath);
        auto locker = initSocialLocker(socialPoolHolder.pool());
        if (!locker.try_lock()) {
            INFO() << "Database is already locker. Task interrupted.";
            return EXIT_SUCCESS;
        }

        auto& txn = locker.writableTxn();
        INFO() << "Deleting unit skips older than " << chrono::formatIsoDate(params.oldestUnitSkipDate);

        const auto deletedSkips = assessment::Gateway(txn).deleteOldUnitSkips(params.oldestUnitSkipDate);
        INFO() << "Deleted " << deletedSkips << " old unit skips";

        if (!params.dryRun) {
            INFO() << "Commit to database...";
            txn.commit();
        }

        INFO() << "Social cleaner, OK";
        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;
    }
}
