#include "util.h"
#include "validator.h"

#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/id.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/id_stream.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/load.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_absent_house_number/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_absent_traffic_light/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_lane_hypothesis/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_traffic_sign/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_wrong_condition/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_wrong_direction/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_wrong_parking/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/generate_wrong_speed_limit/include/generator.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/wiki_config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/collection.h>
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/object/include/revision_loader.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/hypothesis.h>

#include <maps/wikimap/mapspro/libs/revision/include/yandex/maps/wiki/revision/revisionsgateway.h>

#include <maps/infra/yacare/include/parse.h>
#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/log8/include/log8.h>

#include <util/generic/yexception.h>

#include <chrono>
#include <string>

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


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

    maps::cmdline::Parser parser;

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

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

    auto wikiConfigPath = parser.string("wiki-config")
            .help("path to services config for wikimap")
            .required();

    auto bboxOpt = parser.string("bbox")
            .help("region bbox in format lt_x,lt_y~rb_x,rb_y in geodetic coordinates")
            .required();

    auto validationType = parser.string("validate")
            .help("Type of validation")
            .required();

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

    const geolib3::BoundingBox geoBbox = yacare::Parser<geolib3::BoundingBox>()(bboxOpt);

    const auto mrcConfig =
        maps::mrc::common::templateConfigFromCmdPath(secretVersion, mrcConfigPath);
    auto wikiConfig = maps::wiki::common::ExtendedXmlDoc(wikiConfigPath);

    auto mrcPoolHolder = mrcConfig.makePoolHolder();
    maps::wiki::common::PoolHolder wikiPoolHolder{wikiConfig, "long-read", "mrc-eye"};
    maps::wiki::common::PoolHolder socialPoolHolder{wikiConfig, "social", "social"};

    std::unique_ptr<maps::mrc::eye::qa::ValidatorBase> validator;

    if ("lane-hypothesis" == validationType) {
        validator.reset(new maps::mrc::eye::qa::Validator<
                        maps::mrc::eye::LaneHypothesisGeneratorImpl,
                        maps::mrc::db::eye::ObjectType::Sign>());
    } else if ("absent-traffic-light" == validationType) {
        validator.reset(new maps::mrc::eye::qa::Validator<
                        maps::mrc::eye::AbsentTrafficLightGeneratorImpl,
                        maps::mrc::db::eye::ObjectType::TrafficLight>());
    } else if ("absent-house-number" == validationType) {
        validator.reset(new maps::mrc::eye::qa::Validator<
                        maps::mrc::eye::AbsentHouseNumberGeneratorImpl,
                        maps::mrc::db::eye::ObjectType::HouseNumber>());
    } else if ("wrong-speed-limit" == validationType) {
        validator.reset(new maps::mrc::eye::qa::Validator<
                        maps::mrc::eye::SpeedLimitGeneratorImpl,
                        maps::mrc::db::eye::ObjectType::Sign>());
    } else if ("wrong-direction" == validationType) {
        validator.reset(new maps::mrc::eye::qa::Validator<
                        maps::mrc::eye::WrongDirectionGeneratorImpl,
                        maps::mrc::db::eye::ObjectType::Sign>());
    } else if ("wrong-condition" == validationType) {
        validator.reset(new maps::mrc::eye::qa::Validator<
                        maps::mrc::eye::WrongConditionGeneratorImpl,
                        maps::mrc::db::eye::ObjectType::Sign>());
    } else if ("wrong-parking" == validationType) {
        validator.reset(new maps::mrc::eye::qa::Validator<
                        maps::mrc::eye::WrongParkingGeneratorImpl,
                        maps::mrc::db::eye::ObjectType::Sign>());
    } else {
        throw maps::RuntimeError() << "Unknown validation type: " << validationType;
    }

    auto mrcTxn = mrcPoolHolder.pool().slaveTransaction();
    const auto mercBbox = maps::geolib3::convertGeodeticToMercator(geoBbox);
    auto objects = maps::mrc::eye::qa::loadObjects(*mrcTxn, mercBbox);
    INFO() << "Found " << objects.size() << " objects";
    objects = maps::mrc::eye::qa::filterObjects(
        [&](const auto& object) { return validator->appliesToObject(object); },
        std::move(objects));
    INFO() << "Filtered " << objects.size() << " objects";

    auto objectIds = maps::mrc::eye::collectIds(objects);
    auto socialTxn = socialPoolHolder.pool().slaveTransaction();
    const auto oldHypothesesWithFeedback =
        maps::mrc::eye::qa::loadHypothesesWithFeedback(
            *mrcTxn, *socialTxn, objectIds);

    std::unordered_map<
        db::TId,
        std::reference_wrapper<const maps::mrc::eye::qa::HypothesisWithFeedback>>
        objectIdToOldHypothesis;
    objectIdToOldHypothesis.reserve(objects.size());

    for (auto& object : oldHypothesesWithFeedback) {
        objectIdToOldHypothesis.emplace(object.objectId, std::ref(object));
    }

    INFO() << "Loaded " << oldHypothesesWithFeedback.size()
           << " old hypotheses";

    objects = maps::mrc::eye::qa::filterObjects(
        [&](const auto& object) { return objectIdToOldHypothesis.contains(object.id()); },
        std::move(objects));

    INFO() << "Refiltered " << objects.size() << " objects";

    auto objectsCtx = maps::mrc::eye::loadObjectsContext(*mrcTxn, std::move(objects));

    INFO() << "Loaded objects contexts";

    auto loaderFactory = [&](maps::mrc::db::TId objectId) {
        auto revisionId =
            maps::mrc::db::eye::getRevisionId(objectIdToOldHypothesis.at(objectId).get().hypothesis);
        std::optional<std::uint64_t> commitId =
            revisionId.has_value()
                ? std::make_optional(revisionId.value().commitId())
                : std::nullopt;
        return maps::mrc::object::makeRevisionLoader(
            wikiPoolHolder.pool().slaveTransaction(), commitId);
    };

    INFO() << "Validating";
    const auto newHypotheses = maps::mrc::eye::qa::validateInParallel(
        *validator, objectsCtx, loaderFactory);

    INFO() << "Generated hypotheses for " << newHypotheses.size() << " objects";

    auto stat = maps::mrc::eye::qa::calculateStatistics(
        oldHypothesesWithFeedback, newHypotheses);

    std::cout << "Hypotheses statistics:" << std::endl
        << "old cnt:\t" << stat.oldCnt << std::endl
        << "old processed cnt:\t" << stat.oldProcessedCnt << std::endl
        << "old processed with commits cnt:\t" << stat.oldProcessedWithCommitsCnt << std::endl
        << "new cnt:\t" << stat.newCnt << std::endl
        << "new matched:\t" << stat.newMatchedCnt << std::endl
        << "new matched processed:\t" << stat.newMatchedProcessedCnt << std::endl
        << "new matched processed with commits:\t" << stat.newMatchedProcessedWithCommitsCnt << std::endl
        << "--------------------------------------------------------" << std::endl
        << "old usefulness:\t" << ((double) stat.oldProcessedWithCommitsCnt) / stat.oldProcessedCnt << std::endl
        << "new usefulness:\t" << ((double) stat.newMatchedProcessedWithCommitsCnt) / stat.newMatchedProcessedCnt << std::endl
        << "usefull loss:\t" << ((double) stat.oldProcessedWithCommitsCnt - stat.newMatchedProcessedWithCommitsCnt) / stat.oldProcessedWithCommitsCnt  << std::endl
        << "unmached ratio:\t" << ((double) stat.newCnt - stat.newMatchedCnt) / stat.newCnt << std::endl;


    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;
}
