#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.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/track_point.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/track_point_gateway.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/json/include/builder.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/pgpool/include/pgpool3.h>

#include <iostream>

using namespace maps::mrc;

namespace {

db::TrackPoints loadTrackPoints(maps::pgpool3::Pool& pool, const std::string& sourceId, const maps::chrono::TimePoint& start, const maps::chrono::TimePoint& end)
{
    auto txn = pool.slaveTransaction();
    return db::TrackPointGateway{*txn}.load(
        db::table::TrackPoint::sourceId.equals(sourceId) &&
        db::table::TrackPoint::timestamp.between(start, end)
    );
}

db::Features loadFeatures(maps::pgpool3::Pool& pool, const std::string& sourceId, const maps::chrono::TimePoint& start, const maps::chrono::TimePoint& end)
{
    auto txn = pool.slaveTransaction();
    return db::FeatureGateway{*txn}.load(
        db::table::Feature::sourceId.equals(sourceId) &&
        db::table::Feature::isPublished &&
        db::table::Feature::date.between(start, end)
    );
}

void saveTrackPoints(maps::json::ArrayBuilder& builder, const std::string& trackType, const db::TrackPoints& tpts) {
    for (size_t i = 0; i < tpts.size(); i++) {
        const db::TrackPoint& tpt = tpts[i];
        if (tpt.isAugmented()) {
            continue;
        }
        builder << [&](maps::json::ObjectBuilder builder) {
            builder["timestamp"] << maps::chrono::formatSqlDateTime(tpt.timestamp());
            const maps::geolib3::Point2 pt = tpt.geodeticPos();
            builder["lon"] << pt.x();
            builder["lat"] << pt.y();
            builder["type"] << trackType;
            std::optional<double> am = tpt.accuracyMeters();
            if (am) {
                builder["accuracy_meters"] << *am;
            }
            std::optional<maps::geolib3::Heading> heading = tpt.heading();
            if (heading) {
                builder["heading"] << (double)(*heading);
            }
            std::optional<double> speed = tpt.speedMetersPerSec();
            if (speed) {
                builder["speed_meters_per_sec"] << *speed;
            }
        };
    };
}

void saveFeatures(maps::json::ArrayBuilder& builder, const db::Features& features) {
    static const std::string MDS_BASE_PATH = "http://storage-int.mds.yandex.net/get-maps_mrc/";
    for (size_t i = 0; i < features.size(); i++) {
        const db::Feature& feature = features[i];
        builder << [&feature](maps::json::ObjectBuilder builder) {
            builder["feature_id"] << feature.id();
            builder["timestamp"] << maps::chrono::formatSqlDateTime(feature.timestamp());
            const maps::geolib3::Point2 pt = feature.geodeticPos();
            builder["lon"] << pt.x();
            builder["lat"] << pt.y();
            builder["type"] << ((feature.graph() == db::GraphType::Pedestrian) ? "pedestrian" : "vehicle");
            builder["url"] << (MDS_BASE_PATH + feature.mdsGroupId() + "/" + feature.mdsPath());
            builder["orientation"] << (int)feature.orientation();
        };
    };
}

void saveTrack(const std::string& outputPath, const std::string& sourceId, const std::string& trackType, const db::TrackPoints& tpts, const db::Features& features)
{
    std::ofstream ofs(outputPath);
    if (!ofs.is_open())
        ERROR() << "Unable to open output file: " << outputPath;
    maps::json::Builder builder(ofs);
    builder << [&](maps::json::ArrayBuilder builder) {
        builder << [&](maps::json::ObjectBuilder builder) {
            builder["source_id"] << sourceId;
            builder["type"] << trackType;
            builder["track"] << [&](maps::json::ArrayBuilder builder) {
                saveTrackPoints(builder, trackType, tpts);
            };
            if (features.size() > 0) {
                builder["features"] << [&](maps::json::ArrayBuilder builder) {
                    saveFeatures(builder, features);
                };
            };
        };
    };
}

} //namespace

int main(int argc, const char** argv) try {
    maps::cmdline::Parser parser("Extract track by source ID");

    maps::cmdline::Option<std::string> sourceId = parser.string("source-id")
        .required()
        .help("Source ID");

    maps::cmdline::Option<std::string> timeStart = parser.string("time-start")
        .required()
        .help("Start time stamp (format: 2000-08-01 00:00:00.000000+03)");

    maps::cmdline::Option<std::string> timeEnd = parser.string("time-end")
        .required()
        .help("End time stamp (format: 2000-08-01 00:00:00.000000+03)");

    maps::cmdline::Option<std::string> trackType = parser.string("type")
        .help("Type of track (pedestrian/vehicle)");

    maps::cmdline::Option<std::string> mrcConfigPath = parser.string("mrc-config")
        .help("Path to mrc config");

    maps::cmdline::Option<std::string> secretVersion = parser.string("secret-version")
        .help("version for secrets from yav.yandex-team.ru");

    maps::cmdline::Option<bool> saveFeatures = parser.flag("save-features")
        .help("Save features for the sourceId and time interval too");

    maps::cmdline::Option<std::string> outputPath = parser.string("output-path")
        .required()
        .help("Path to json for save data");

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

    maps::mrc::common::Config mrcConfig =
        secretVersion.defined()
        ? maps::mrc::common::Config(maps::vault_boy::loadContextWithYaVault(secretVersion), mrcConfigPath)
        : maps::mrc::common::templateConfigFromCmdPath(mrcConfigPath);
    maps::wiki::common::PoolHolder mrc(mrcConfig.makePoolHolder());

    REQUIRE(trackType.defined() && (trackType == "pedestrian" || trackType == "vehicle"),
            "Track type should be vehicle or pedestrian");
    db::TrackPoints tpts =
        loadTrackPoints(mrc.pool(), sourceId,
                        maps::chrono::parseSqlDateTime(timeStart),
                        maps::chrono::parseSqlDateTime(timeEnd));
    INFO() << "Loaded: " << tpts.size() << " points";
    std::sort(tpts.begin(), tpts.end(),
        [](const db::TrackPoint& a, const db::TrackPoint& b) {
            return a.timestamp() < b.timestamp();
        }
    );

    db::Features features;
    if (saveFeatures) {
        features = loadFeatures(mrc.pool(), sourceId,
                     maps::chrono::parseSqlDateTime(timeStart),
                     maps::chrono::parseSqlDateTime(timeEnd));
        INFO() << "Loaded: " << features.size() << " features";
        std::sort(features.begin(), features.end(),
            [](const db::Feature& a, const db::Feature& b) {
                return a.timestamp() < b.timestamp();
            }
        );
    }
    saveTrack(outputPath, sourceId, trackType, tpts, features);
    return EXIT_SUCCESS;
}
catch (const maps::Exception& e) {
    FATAL() << "Worker failed: " << e;
    return EXIT_FAILURE;
}
catch (const std::exception& e) {
    FATAL() << "Worker failed: " << e.what();
    return EXIT_FAILURE;
}
