#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/concurrent/include/scoped_guard.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/pg_locks.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/threadpool_wrapper.h>
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/metadata_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/fb/include/version.h>
#include <maps/wikimap/mapspro/services/mrc/libs/privacy/include/region_privacy.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/export_gen/lib/ecstatic.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/export_gen/lib/exporter.h>
#include <yandex/maps/pgpool3utils/pg_advisory_mutex.h>
#include <yandex/maps/wiki/common/pgpool3_helpers.h>

using namespace maps::mrc;
using namespace export_gen;

namespace {

const std::string DEFAULT_TEMP_DIR = "/var/tmp/mrc_export";
const std::string DEFAULT_ROAD_GRAPH_DIR = "/usr/share/yandex/maps/graph";
const std::string DEFAULT_PEDESTRIAN_GRAPH_DIR = "/var/lib/yandex/maps/ecstatic/data/yandex-maps-pedestrian-graph-fb";
const std::string DEFAULT_MRC_DATASET_DIR = "/var/lib/yandex/maps/ecstatic/data/yandex-maps-mrc";
const std::string DEFAULT_MRC_FEATURES_SECRET_DATASET_DIR = "/var/lib/yandex/maps/ecstatic/data/yandex-maps-mrc-features-secret";
const std::string DEFAULT_MRC_GRAPH_PRO_DATASET_DIR = "/var/lib/yandex/maps/ecstatic/data/yandex-maps-mrc-graph-pro";
const std::string DEFAULT_MRC_PEDESTRIAN_GRAPH_PRO_DATASET_DIR = "/var/lib/yandex/maps/ecstatic/data/yandex-maps-mrc-pedestrian-graph-pro";
const std::string DEFAULT_MRC_PHOTO_TO_EDGE_PRO_DATASET_DIR = "/var/lib/yandex/maps/ecstatic/data/yandex-maps-mrc-photo-to-edge-pro";
const std::string DEFAULT_MRC_PHOTO_TO_PEDESTRIAN_EDGE_PRO_DATASET_DIR = "/var/lib/yandex/maps/ecstatic/data/yandex-maps-mrc-photo-to-pedestrian-edge-pro";

} // namespace

int main(int argc, const char** argv) try
{
    maps::cmdline::Parser parser(
        "This service exports essential data from db to a set of fb files "
        "which are then published via ecstatic."
    );
    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 tempDir = parser.dir("temp-dir")
        .help("temporary directory to put export files")
        .defaultValue(DEFAULT_TEMP_DIR);
    auto roadGraphDir = parser.dir("road-graph-dir")
        .help("road graph directory")
        .defaultValue(DEFAULT_ROAD_GRAPH_DIR);
    auto pedestrianGraphDir = parser.dir("pedestrian-graph-dir")
        .help("pedestrian graph directory")
        .defaultValue(DEFAULT_PEDESTRIAN_GRAPH_DIR);
    auto mrcDatasetDir = parser.dir("mrc-dataset-dir")
        .help("yandex-maps-mrc dataset directory")
        .defaultValue(DEFAULT_MRC_DATASET_DIR);
    auto mrcFeaturesSecretDatasetDir = parser.dir("mrc-features-secret-dataset-dir")
        .help("yandex-maps-mrc-features-secret dataset directory")
        .defaultValue(DEFAULT_MRC_FEATURES_SECRET_DATASET_DIR);
    auto mrcGraphProDatasetDir = parser.dir("mrc-graph-pro-dataset-dir")
        .help("yandex-maps-mrc-graph-pro dataset directory")
        .defaultValue(DEFAULT_MRC_GRAPH_PRO_DATASET_DIR);
    auto mrcPedestrianGraphProDatasetDir = parser.dir("mrc-pedestrian-graph-pro-dataset-dir")
        .help("yandex-maps-mrc-pedestrian-graph-pro dataset directory")
        .defaultValue(DEFAULT_MRC_PEDESTRIAN_GRAPH_PRO_DATASET_DIR);
    auto mrcPhotoToEdgeProDatasetDir = parser.dir("mrc-photo-to-edge-pro-dataset-dir")
        .help("yandex-maps-mrc-photo-to-edge-pro dataset directory")
        .defaultValue(DEFAULT_MRC_PHOTO_TO_EDGE_PRO_DATASET_DIR);
    auto mrcPhotoToPedestrianEdgeProDatasetDir = parser.dir("mrc-photo-to-pedestrian-edge-pro-dataset-dir")
        .help("yandex-maps-mrc-photo-to-pedestrian-edge-pro dataset directory")
        .defaultValue(DEFAULT_MRC_PHOTO_TO_PEDESTRIAN_EDGE_PRO_DATASET_DIR);
    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 or publish data to social");
    parser.parse(argc, const_cast<char**>(argv));

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

    auto mrcConfig =
        common::templateConfigFromCmdPath(secretVersion, configPath);
    auto poolHolder = maps::wiki::common::PoolHolder(mrcConfig.makePoolHolder(
        common::LONG_READ_DB_ID, common::LONG_READ_POOL_ID));
    maps::pgp3utils::PgAdvisoryXactMutex mutex(
        poolHolder.pool(),
        static_cast<int64_t>(common::LockId::ExportGenerator));
    if (!dryRun && !mutex.try_lock()) {
        INFO() << "Another process is ongoing";
        return EXIT_SUCCESS;
    }
    auto ecstaticClient = common::EcstaticClient(
            common::EcstaticClient::ECSTATIC_PATH,
            mrcConfig.externals().ecstaticEnvironmentOverride());
    auto ecstaticBackground = common::ThreadpoolWrapper{1u /* threadsNumber */};
    auto version = fb::makeVersion(maps::chrono::TimePoint::clock::now());
    INFO() << "Export version: " << version;

    auto mrcOutputPath = tempDir + "/" + ECSTATIC_MRC_DATASET_NAME;
    auto featuresSecretOutputPath = tempDir + "/" + ECSTATIC_FEATURES_SECRET_DATASET_NAME;
    auto txnId = getLastFeatureTxnId(poolHolder.pool());
    auto prevTxnId =
        db::MetadataGateway{*poolHolder.pool().slaveTransaction()}.tryLoadByKey(
            LAST_TXN_ID, db::TId{});
    auto featuresSummary = generateExport(
        *makeLoader(poolHolder.pool(), mrcDatasetDir, mrcFeaturesSecretDatasetDir, prevTxnId),
        mrcOutputPath,
        featuresSecretOutputPath,
        version,
        txnId);
    if (!dryRun) {
        ecstaticBackground->add([=, &ecstaticClient] {
        publish(ecstaticClient, ECSTATIC_MRC_DATASET_NAME, version, mrcOutputPath);
        publish(ecstaticClient, ECSTATIC_FEATURES_SECRET_DATASET_NAME, version, featuresSecretOutputPath);
        });
    }

    GraphDescriptor graphDescriptors[] = {
        {.graphDir = roadGraphDir,
         .graphType = db::GraphType::Road,
         .prevGraphDir = mrcGraphProDatasetDir,
         .prevPhotoToEdgeDir = mrcPhotoToEdgeProDatasetDir},
        {.graphDir = pedestrianGraphDir,
         .graphType = db::GraphType::Pedestrian,
         .prevGraphDir = mrcPedestrianGraphProDatasetDir,
         .prevPhotoToEdgeDir = mrcPhotoToPedestrianEdgeProDatasetDir},
    };

    for (const auto& graphDescriptor : graphDescriptors) {
        using namespace std::string_literals;
        bool isPedestrian =
            graphDescriptor.graphType == db::GraphType::Pedestrian;

        auto datasetGraph = "yandex-maps-mrc-"s + (isPedestrian ? "pedestrian-"s : ""s) + "graph"s;
        auto datasetGraphPro = datasetGraph + "-pro";
        auto datasetPhotoToEdge = "yandex-maps-mrc-photo-to-"s + (isPedestrian ? "pedestrian-"s : ""s) + "edge"s;
        auto datasetPhotoToEdgePro = datasetPhotoToEdge + "-pro";

        auto destGraph = tempDir + "/" + datasetGraph;
        auto destGraphPro = tempDir + "/" + datasetGraphPro;
        auto destPhotoToEdge = tempDir + "/" + datasetPhotoToEdge;
        auto destPhotoToEdgePro = tempDir + "/" + datasetPhotoToEdgePro;

        generateGraph(graphDescriptor,
                      poolHolder.pool(),
                      featuresSummary.photos,
                      destGraph,
                      destGraphPro,
                      destPhotoToEdge,
                      destPhotoToEdgePro,
                      version,
                      featuresSummary.fbVersion);
        if (!dryRun) {
            ecstaticBackground->add([=, &ecstaticClient] {
            publish(ecstaticClient, datasetPhotoToEdgePro, version, destPhotoToEdgePro);
            publish(ecstaticClient, datasetGraphPro, version, destGraphPro);
            publish(ecstaticClient, datasetPhotoToEdge, version, destPhotoToEdge);
            publish(ecstaticClient, datasetGraph, version, destGraph);
            });
        }
    }

    const std::string activationOutputPath = tempDir + "/" + ECSTATIC_ACTIVATION_NAME;
    generateActivation(activationOutputPath, version);
    if (!dryRun) {
        ecstaticBackground->drain();
        ecstaticBackground.checkExceptions();
        publish(ecstaticClient, ECSTATIC_ACTIVATION_NAME, version, activationOutputPath);
        waitForActivation(ecstaticClient, ECSTATIC_MRC_DATASET_NAME, version);
        waitForActivation(ecstaticClient, ECSTATIC_FEATURES_SECRET_DATASET_NAME, version);
        waitForActivation(ecstaticClient, ECSTATIC_ACTIVATION_NAME, version);
        markAsPublished(featuresSummary.newToPublish, poolHolder.pool());
        updateMetadata(poolHolder.pool(), txnId);
    }

    INFO() << "Done export version: " << version;
    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    FATAL() << "Export failed: " << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    FATAL() << "Export failed: " << e.what();
    return EXIT_FAILURE;
}
