#include <mapreduce/yt/interface/client.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/geo_helpers.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/graph_load.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/hyp_converter.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/hyp_generator.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/hyp_poster.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/consts.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/revision_roads_miner_db.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/revision_snapshot_holder.h>
#include <maps/wikimap/mapspro/tools/traffic_analyzer/lib/yt_ops.h>

#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/pgpool3utils/pg_advisory_mutex.h>
#include <yandex/maps/wiki/common/default_config.h>
#include <yandex/maps/wiki/common/extended_xml_doc.h>
#include <yandex/maps/wiki/common/geom_utils.h>
#include <yandex/maps/wiki/common/pg_advisory_lock_ids.h>
#include <yandex/maps/wiki/common/pgpool3_helpers.h>

namespace bg = boost::gregorian;
namespace mwc = maps::wiki::common;
namespace mwt = maps::wiki::traffic_analyzer;
namespace mgl = maps::geolib3;

const std::string SOCIAL_SERVICE_URL = "/config/services/social-backoffice/url";

const int CODE_SUCCESS = 0;
const int CODE_LOCKED = 1;
const int CODE_FAILURE = 2;


int main(int argc, char* argv[]) try
{
    NYT::Initialize(argc, const_cast<const char**>(argv));

    maps::cmdline::Parser parser;

    auto config = parser.string("config")
        .help("Path to configuration file of revision db and social service");

    auto lastSignalsDayOpt = parser.string("last-signals-day")
        .help("Last signals day, which will be taken into account for "
              "matching statistics. If defaulted - "
              "the day before yesterday will be considered, "
              "because it relies on jams tracks matching.");

    auto averagingWindow = parser.num("average-window")
        .defaultValue(1)
        .help("Averaging days window size. "
              "For example. If equal to 1 - only last day signals will be "
              "analyzed. If equal to 2 - last day and the day before last. "
              "And so on. If defaulted - equal to 1");

    auto maxHypsNumOpt = parser.num("max-hyps")
        .help("Maximal number of generated hypotheses. "
              "If defaulted - all generated hypotheses will be published.");

    auto bboxGeoString = parser.string("bbox-geo")
        .defaultValue(mwt::WORLD_BBOX_GEO_STRING)
        .help("Comma separated string with bounding box (Geo) "
              "of hypotheses area. String format: lon1,lat1,lon2,lat2. "
              "If defaulted - the whole world will be analyzed");

    auto alarmMatchRatio = parser.real("alarm-match-ratio")
        .help("Hypothesis parameter. If matched ratio of tracks for particular "
              "road is greater than this value, a hypothesis will be produced")
        .required();

    auto minBothEdgeTracks = parser.num("min-both-edge-tracks")
        .help("Hypothesis parameter. If number of matched tracks for particular "
              "road (on both directions) is less than this value, "
              "no hypothesis will be produced")
        .required();

    auto maxFunClass = parser.num("max-funclass")
        .help("Hypothesis parameter. If fun-class of particular road is "
              "greater than this value, no hypothesis will be produced")
        .required();

    parser.parse(argc, argv);

    // Load config
    std::unique_ptr<mwc::ExtendedXmlDoc> configXml;
    if (config.defined()) {
        configXml.reset(new mwc::ExtendedXmlDoc(config));
    } else {
        configXml = mwc::loadDefaultConfig();
    }

    // Make this process exclusive between worker hosts
    mwc::PoolHolder corePoolHolder(*configXml, "core", "core");
    maps::pgp3utils::PgAdvisoryXactMutex locker(corePoolHolder.pool(),
        static_cast<int64_t>(mwc::AdvisoryLockIds::ONEWAY_HYPS_PRODUCER));
    if (!locker.try_lock()) {
        INFO() << "Database is already locked. Task interrupted.";
        return CODE_LOCKED;
    }

    // Calc signals days period
    bg::date lastSignalsDay;
    if (lastSignalsDayOpt.defined()) {
        lastSignalsDay = bg::from_string(lastSignalsDayOpt);
    } else {
        lastSignalsDay = mwt::latestMatchedSignalsDay();
    }

    bg::date_period signalsDays(
        lastSignalsDay - bg::date_duration(averagingWindow - 1),
        lastSignalsDay + bg::date_duration(1)
    );

    // Take matching statistics from YT
    INFO() << "Mining matched tracks statistics for graph roads";
    auto persIdsToTracks = mwt::getYTRoadsStat(signalsDays);

    // Choosing graph version
    auto graphVersion = mwt::oldestGraphVersionOfMatchedTracks(signalsDays);

    // Choose roads from bbox
    INFO() << "Loading static road graph";
    mwt::GraphMmapped graph = mwt::loadGraph(graphVersion);

    INFO() << "Loading graph roads from bounding box";
    auto bboxGeo = mwc::bboxFromCommaSeparatedCoords(bboxGeoString);
    auto twoWayRoads = graph.getTwoWayRoads(bboxGeo);

    // Produce hypotheses
    INFO() << "Generating one-way hypotheses for static road graph";
    mwt::OnewayGenParams genParams(alarmMatchRatio, maxFunClass, minBothEdgeTracks);
    mwt::OnewayHypothesisGenerator gen(graph, std::move(persIdsToTracks), genParams);

    auto hypsGraph = gen.generate(twoWayRoads);
    INFO() << "Generated road graph hypotheses: " << hypsGraph.size();

    // Convert hypotheses
    mwt::RevisionSnapshotHolder snapshotHolder(*configXml);
    auto revRoadsMiner =
        std::make_unique<mwt::RevisionRoadsMinerDB>(snapshotHolder.snapshot());
    mwt::OnewayHypothesisConverter converter(graph, std::move(revRoadsMiner));

    INFO() << "Converting one-way hypotheses for static road graph";
    auto hypsRev = mwt::convertHypsGraphToRevision(hypsGraph, converter);
    INFO() << "Converted revision hypotheses: " << hypsRev.size();

    // Publish hypotheses
    INFO() << "Publish one-way hypotheses for static road graph";
    mwt::HypothesisRevisionPoster poster(configXml->get<std::string>(SOCIAL_SERVICE_URL));

    auto maxHypsNum = hypsRev.size();
    if (maxHypsNumOpt.defined()) {
        maxHypsNum = maxHypsNumOpt;
    }

    int hypsPostedNum = mwt::postHypothesesLimited(poster, hypsRev, maxHypsNum);
    INFO() << "Published hypotheses: " << hypsPostedNum;

    return CODE_SUCCESS;

} catch (const maps::Exception& e) {
    FATAL() << e;
    return CODE_FAILURE;
} catch (const std::exception& e) {
    FATAL() << e.what();
    return CODE_FAILURE;
} catch (...) {
    FATAL() << "unknown exception";
    return CODE_FAILURE;
}
