#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/ride_recording_report_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/ugc/gateway.h>

#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/http/include/http.h>
#include <maps/libs/log8/include/log8.h>

#include <maps/libs/common/include/exception.h>
#include <yandex/maps/proto/offline_recording/record.pb.h>
#include <yandex/maps/pb_stream2/reader.h>

#include <filesystem>
#include <fstream>
#include <memory>
#include <sstream>
#include <vector>

namespace mrc = maps::mrc;

namespace {

uint64_t loadRecordTimestamp(std::stringstream& stream)
{
    namespace precord = yandex::maps::proto::offline::recording::record;
    maps::pb_stream2::Reader reader(&stream);
    auto it = reader.begin();
    ASSERT(it != reader.end());
    return it->as<precord::Record>().timestamp();
}

} // namespace

int main(int argc, char** argv)
try {

    maps::cmdline::Parser parser("Tool for downloading recordings of one ride to a file");
    auto configPath = parser.string("config")
        .help("path to configuration");
    auto fromFeatureId = parser.num("from-feature").required();
    auto toFeatureId = parser.num("to-feature");
    auto secretVersion = parser.string("secret-version")
            .required()
            .help("version for secrets from yav.yandex-team.ru");
    auto output = parser.string("output").required().help("output filename");

    parser.parse(argc, argv);

    REQUIRE(!std::filesystem::exists(static_cast<std::string>(output)), "File " << output << "already exists");

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

    auto poolHolder = config.makePoolHolder();

    auto txn = poolHolder.pool().slaveTransaction();
    auto fromFeature = mrc::db::FeatureGateway(*txn).loadById(fromFeatureId);

    std::vector<maps::mds::Key> keys;

    if (fromFeature.dataset() == mrc::db::feature::dataset::AGENTS) {
        auto assignmentFeature = mrc::db::FeatureGateway(*txn).loadById(fromFeatureId);
        REQUIRE(assignmentFeature.assignmentId().has_value(), "empty assignmentId");
        auto recordings = mrc::db::ugc::AssignmentRecordingReportGateway(*txn)
            .load(mrc::db::ugc::table::AssignmentRecordingReport::assignmentId.equals(assignmentFeature.assignmentId().value())
                    && mrc::db::ugc::table::AssignmentRecordingReport::sourceId.equals(fromFeature.sourceId()));

        keys.reserve(recordings.size());
        for (const auto& recording : recordings) {
            keys.push_back(recording.mdsKey());
        }

    } else if (fromFeature.dataset() == mrc::db::feature::dataset::RIDES) {
        REQUIRE(toFeatureId.defined(), "to-feature should be defined for 'rides'");
        auto toFeature = mrc::db::FeatureGateway(*txn).loadById(toFeatureId);
        REQUIRE(fromFeature.sourceId() == toFeature.sourceId(),
            "Features are from different sources");

        auto [minTime, maxTime] = std::minmax(fromFeature.timestamp(), toFeature.timestamp());

        auto recordings =  mrc::db::rides::RideRecordingReportGateway(*txn)
            .load(mrc::db::rides::table::RideRecordingReport::sourceId.equals(fromFeature.sourceId())
                && mrc::db::rides::table::RideRecordingReport::finishedAt >= minTime
                && mrc::db::rides::table::RideRecordingReport::startedAt <= maxTime);

        keys.reserve(recordings.size());
        for (const auto& recording : recordings) {
            keys.push_back({recording.mdsGroupId(), recording.mdsPath()});
        }
    } else {
        REQUIRE(false, "Unsupported dataset " << fromFeature.dataset());
    }

    INFO() << "There are " << keys.size() << " files to download";
    auto mdsClient = config.makeMdsClient();
    using TimestampStreamPair = std::pair<uint64_t, std::stringstream>;
    std::vector<TimestampStreamPair> reportsStr;
    reportsStr.reserve(keys.size());

    for (const auto& key : keys) {
        std::stringstream sstr;
        mdsClient.get(key, sstr);
        uint64_t timestamp = loadRecordTimestamp(sstr);
        reportsStr.emplace_back(timestamp, std::move(sstr));
    }
    INFO() << "Downloaded";

    std::sort(std::begin(reportsStr), std::end(reportsStr),
        [](const TimestampStreamPair& one, const TimestampStreamPair& other) {
            return one.first < other.first;
        }
    );
    std::ofstream file(output, std::ios_base::binary);
    for (auto& [ts, sstr] : reportsStr) {
        file << sstr.str();
    }

    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    ERROR() << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    ERROR() << e.what();
    return EXIT_FAILURE;
} catch (...) {
    ERROR() << "Caught unknown exception";
    return EXIT_FAILURE;
}
