#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/common/include/pg_locks.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/firmware_upload_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/firmware_upload_part_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/storage/lib/s3/client.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/pgpool/include/pgpool3.h>
#include <maps/libs/common/include/exception.h>
#include <yandex/maps/pgpool3utils/pg_advisory_mutex.h>

using namespace maps::fw_updater;

namespace {

constexpr std::chrono::hours MAX_UPLOAD_AGE{24};

void deleteUploads(maps::pgpool3::Pool& pool, storage::s3::S3Client& s3Client, bool dryRun)
{
    const auto timePoint = maps::chrono::TimePoint::clock::now() - MAX_UPLOAD_AGE;

    INFO() << "Deleting uploads created before " << maps::chrono::formatSqlDateTime(timePoint);

    auto txn = pool.masterWriteableTransaction();
    db::FirmwareUploadGateway fwUploadGtw{*txn};
    db::FirmwareUploadPartGateway fwUploadPartGtw{*txn};

    auto uploads = fwUploadGtw.load(db::table::FirmwareUpload::createdAt < timePoint);

    if (uploads.empty()) {
        INFO() << "No unfinished uploads found";
        return;
    }

    for (const auto& upload : uploads) {
        const db::TId id = upload.id();
        INFO() << "Deleting upload id=" << id << ", s3key=" << upload.s3Key();
        if (!dryRun) {
            s3Client.abortMultipartUpload(upload.s3Key(), upload.s3UploadId());
        }
        fwUploadPartGtw.remove(db::table::FirmwareUploadPart::uploadId == id);
        fwUploadGtw.removeById(id);
    }

    INFO() << "Deleted " << uploads.size() << " unfinished uploads";

    if (!dryRun) {
        txn->commit();
        INFO() << "Committed";
    } else {
        INFO() << "Skip commit in dry run mode";
    }
}

} // namespace

int main(int argc, const char** argv) try
{
    maps::cmdline::Parser parser("Tool to clean up abandoned unfinished firmware uploads.");
    auto syslog = parser.string("syslog-tag")
        .help("redirect log output to syslog with given tag");
    auto configPath = parser.string("config")
        .help("path to configuration");
    auto secretVersion = parser.string("secret-version")
        .help("version for secrets from yav.yandex-team.ru");
    auto dryRun = parser.flag("dry-run")
        .help("do not save changes to database");

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

    if (syslog.defined()) {
        maps::log8::setBackend(maps::log8::toSyslog(syslog));
    }

    auto config = config::templateConfigFromCmdPath(secretVersion, configPath);
    auto pool = config.makePoolHolder();
    maps::pgp3utils::PgAdvisoryXactMutex mutex(
        pool.pool(),
        static_cast<int64_t>(common::LockId::UnfinishedUploadsCleaner));
    if (!dryRun && !mutex.try_lock()) {
        INFO() << "Another process is ongoing";
        return EXIT_SUCCESS;
    }

    storage::s3::S3Client s3Client{config.s3()};

    deleteUploads(pool.pool(), s3Client, dryRun);

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