#include <maps/wikimap/mapspro/services/mrc/eye/lib/feedback/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/feedback/include/worker.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/id_stream.h>

#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>

#include <chrono>
#include <thread>

using namespace maps;
using namespace maps::mrc;
using namespace maps::mrc::eye;

int main(int argc, const char** argv) try {
    maps::cmdline::Parser parser;

    auto syslog = parser.string("syslog-tag")
            .help("redirect log output to syslog with given tag");

    auto mrcConfigPath = parser.string("mrc-config")
            .help("path to mrc config");

    auto secretVersion = parser.string("secret-version")
            .help("version for secrets from yav.yandex-team.ru");

    auto hypothesisIdsPath = parser.string("hypothesis-ids")
            .help("path to hypothesis ids file");

    auto loop = parser.flag("loop")
            .help("work in infinite loop");

    auto batchSize = parser.num("batch")
            .defaultValue(1000)
            .help("hypothesis batch size (default 1000)");

    auto commit = parser.flag("commit")
            .help("commit results");

    auto lockFree = parser.flag("lock-free")
            .help("lock free import (may be dangerous)");

    auto timeout = parser.num("timeout")
            .help("wait for db update in minutes (default 5)")
            .defaultValue(5);

    auto enableAbsentTrafficLight = parser.flag("enable-absent-traffic-light")
        .help("enable push absent traffic light hypotheses");

    auto enableAbsentHouseNumber = parser.flag("enable-absent-house-number")
        .help("enable push absent house number hypotheses");

    auto enableWrongSpeedLimit = parser.flag("enable-wrong-speed-limit")
        .help("enable push wrong speed limit hypotheses");

    auto enableAbsentParking = parser.flag("enable-absent-parking")
        .help("enable push absent parking hypotheses");

    auto enableWrongParkingFtType = parser.flag("enable-wrong-parking-ft-type")
        .help("enable push wrong parking ft type hypotheses");

    auto enableTrafficSign = parser.flag("enable-traffic-sign")
        .help("enable push traffic sign hypotheses");

    auto enableWrongDirection = parser.flag("enable-wrong-direction")
        .help("enable push wrong direction hypotheses");

    auto enableProhibitedPath = parser.flag("enable-prohibited-path")
        .help("enable push prohibited path hypotheses");

    auto enableLaneHypothesis = parser.flag("enable-lane-hypothesis")
        .help("enable push lane hypotheses");

    auto enableSpeedBump = parser.flag("enable-speed-bump")
        .help("enable push speed bump");

    auto enableApproveInToloka = parser.flag("approve-in-toloka")
        .help("Approve hypotheses in Toloka");

    auto enablePushToSocial = parser.flag("push-to-social")
        .help("Push hypotheses to social");

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

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

    const auto config =
        maps::mrc::common::templateConfigFromCmdPath(secretVersion,
                                                     mrcConfigPath);

    wiki::common::PoolHolder poolHolder = config.makePoolHolder();
    FrameUrlResolver frameUrlResolver(
        config.externals().mrcBrowserUrl(),
        config.externals().mrcBrowserProUrl());
    FeedbackUrlResolver feedbackUrlResolver(
        config.externals().socialBackofficeUrl());

    db::eye::HypothesisTypes hypothesisTypes;
    if (enableAbsentTrafficLight) {
        hypothesisTypes.push_back(db::eye::HypothesisType::AbsentTrafficLight);
    }
    if (enableAbsentHouseNumber) {
        hypothesisTypes.push_back(db::eye::HypothesisType::AbsentHouseNumber);
    }
    if (enableWrongSpeedLimit) {
        hypothesisTypes.push_back(db::eye::HypothesisType::WrongSpeedLimit);
    }
    if (enableAbsentParking) {
        hypothesisTypes.push_back(db::eye::HypothesisType::AbsentParking);
    }
    if (enableWrongParkingFtType) {
        hypothesisTypes.push_back(db::eye::HypothesisType::WrongParkingFtType);
    }
    if (enableTrafficSign) {
        hypothesisTypes.push_back(db::eye::HypothesisType::TrafficSign);
    }
    if (enableWrongDirection) {
        hypothesisTypes.push_back(db::eye::HypothesisType::WrongDirection);
    }
    if (enableProhibitedPath) {
        hypothesisTypes.push_back(db::eye::HypothesisType::ProhibitedPath);
    }
    if (enableLaneHypothesis) {
        hypothesisTypes.push_back(db::eye::HypothesisType::LaneHypothesis);
    }
    if (enableSpeedBump) {
        hypothesisTypes.push_back(db::eye::HypothesisType::SpeedBump);
    }

    PushFeedbackConfig workerConfig;
    workerConfig.mrc.pool = &poolHolder.pool();
    workerConfig.mrc.commit = commit;
    workerConfig.mrc.lockFree = lockFree;
    workerConfig.frameUrlResolver = &frameUrlResolver;
    workerConfig.feedbackUrlResolver = &feedbackUrlResolver;
    workerConfig.geoIdProvider = privacy::makeGeoIdProvider(config.externals().geoIdCoveragePath());
    workerConfig.hypothesisTypes = hypothesisTypes;
    workerConfig.approveInToloka = enableApproveInToloka;
    workerConfig.pushToSocial = enablePushToSocial;

    PushFeedbackWorker worker(workerConfig);

    if (not loop) {
        if (hypothesisIdsPath.defined()) {
            for (IdStream idStream(hypothesisIdsPath); idStream.valid(); ) {
                worker.processBatch(idStream.readBatch(batchSize));
            }
        } else {
            WARN() << "No hypothesis ids!";
        }

        return EXIT_SUCCESS;
    }

    INFO() << "Infinite loop mode...";
    if (hypothesisIdsPath.defined()) {
        WARN() << "Ignore hypothesis ids '" << hypothesisIdsPath << "'";
    }

    worker.runInLoopMode(batchSize, std::chrono::minutes(timeout));

    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    FATAL() << e;
    return EXIT_FAILURE;
} catch (const yexception& e) {
    FATAL() << e.what();

    if (e.BackTrace()) {
        FATAL() << e.BackTrace()->PrintToString();
    }

    return EXIT_FAILURE;
} catch (const std::exception& e) {
    FATAL() << e.what();
    return EXIT_FAILURE;
} catch (...) {
    FATAL() << "Unknown error!";
    return EXIT_FAILURE;
}
