#include "yt.h"

#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/metadata_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/object_in_photo_gateway.h>
#include <maps/libs/common/include/make_batches.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/pg_locks.h>
#include <maps/libs/log8/include/log8.h>
#include <mapreduce/yt/interface/client.h>

#include <maps/libs/sql_chemistry/include/order.h>

namespace db = maps::mrc::db;

namespace {

const std::string APP_NAME = "privacy_detector.tool";
const std::string LAST_RUN_TIME = APP_NAME + ".timestamp";
const std::string LAST_FEATURE_ID = APP_NAME + ".feature_id";

// Staring from this feature ID image-analyzer-yt was enabled
const db::TId MAX_UNPROCESSED_FEATURE_ID = 51952440;

constexpr std::size_t FEATURES_LIMIT = 50000;

db::Features loadFeatures(maps::pgpool3::Pool& pool,
                          db::TId startId,
                          size_t limit) {
    auto txn = pool.slaveTransaction();

    INFO() << "Load features from db";
    db::FeatureGateway gateway(*txn);

    auto ids = gateway.loadIds(
         db::table::Feature::id > startId && db::table::Feature::id < MAX_UNPROCESSED_FEATURE_ID,
         maps::sql_chemistry::orderBy(db::table::Feature::id).asc().limit(limit));

    INFO() << "Loaded ids count: " << ids.size();

    return gateway.loadByIds(ids);
}

void save(db::ObjectsInPhoto& objects,
          db::TId lastId,
          maps::pgpool3::Pool& pool)
{
    INFO() << "Save " << objects.size() << " privacy objects to db";
    auto txn = pool.masterWriteableTransaction();
    db::ObjectInPhotoGateway{*txn}.insert(objects);

    INFO() << "Update metadata";

    db::MetadataGateway metadataGtw{*txn};
    metadataGtw.upsertByKey(LAST_RUN_TIME,
        maps::chrono::formatSqlDateTime(maps::chrono::TimePoint::clock::now()));
    metadataGtw.upsertByKey(LAST_FEATURE_ID, std::to_string(lastId));

    txn->commit();

}

} // namespace

int main(int argc, const char** argv) try
{
    NYT::Initialize(argc, argv);

    maps::cmdline::Parser parser(
        "This service runs image classifiers/detectors on every feature which"
        " is not yet processed in the database. Classifiers are run on YT."
        " The results are saved back to the database."
    );
    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 startId = parser.num("start-id")
        .help("start from feature id. If not specified, taken from db metadata");
    auto batchSize = parser.num("batch-size")
        .help("features batch size. If not specified equals 50000");

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

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

    auto mrcConfig = maps::mrc::common::templateConfigFromCmdPath(configPath);

    maps::wiki::common::PoolHolder poolHolder(mrcConfig.makePoolHolder());

    size_t limit = batchSize ? (size_t)batchSize : FEATURES_LIMIT;

    db::TId id = 0UL;
    if (startId.defined()) {
        id = startId;
    } else {
        auto txn = poolHolder.pool().slaveTransaction();
        auto savedId = db::MetadataGateway{*txn}.tryLoadByKey(LAST_FEATURE_ID);
        id = savedId ? std::stoul(*savedId) : 0UL;
    }

    while (true) {
        db::Features features = loadFeatures(poolHolder.pool(), id, limit);
        if (features.empty()) {
            INFO() << "Finished";
            return EXIT_SUCCESS;
        }

        INFO() << "Start processing batch"
               << " from feature id = " << features.front().id()
               << " to feature id = " << features.back().id();

        auto objects = maps::mrc::image_analyzer::detectObjectsOnYT(features, mrcConfig);

        id = features.back().id();
        save(objects, id, poolHolder.pool());
    }

    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    FATAL() << "Worker failed: " << e;
    return EXIT_FAILURE;
} catch (const yexception& e) {
    FATAL() << "Worker failed: " << e.what();
    if (e.BackTrace()) {
        FATAL() << e.BackTrace()->PrintToString();
    }
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    FATAL() << "Worker failed: " << e.what();
    return EXIT_FAILURE;
}
