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

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

#include <vector>

namespace precording = yandex::maps::proto::offline::recording;
namespace ugc = maps::mrc::db::ugc;
namespace rides = maps::mrc::db::rides;

namespace maps::mrc::sensors_feature_positioner {

namespace {

const TString SENSORS_VERSION = "sensors3";
const TString OLD_SENSORS_VERSION_1 = "sensors";
const TString OLD_SENSORS_VERSION_2 = "sensors2";
const TString SENSOR_TYPE_ACCELEROMETER = "accel";
const TString SENSOR_TYPE_GYROSCOPE = "gyro";

struct SensorRecord {
    TString type;
    double x;
    double y;
    double z;
    chrono::TimePoint time;
};
using SensorRecords = std::vector<SensorRecord>;

std::vector<rides::RideRecordingReport> loadReports(
    pgpool3::Pool& pool,
    const std::string& sourceId,
    chrono::TimePoint minTime,
    chrono::TimePoint maxTime)
{
   auto txn = pool.slaveTransaction();
   rides::RideRecordingReportGateway gtw(*txn);
   return gtw.load(
       rides::table::RideRecordingReport::sourceId.equals(sourceId)
       && rides::table::RideRecordingReport::finishedAt >= minTime
       && rides::table::RideRecordingReport::startedAt <= maxTime);
}

std::vector<ugc::AssignmentRecordingReport> loadReports(
    pgpool3::Pool& pool,
    std::string sourceId,
    db::TId assignmentId)
{
   auto txn = pool.slaveTransaction();
   ugc::AssignmentRecordingReportGateway gtw(*txn);
   return gtw.load(
       ugc::table::AssignmentRecordingReport::assignmentId.equals(assignmentId)
       && ugc::table::AssignmentRecordingReport::sourceId.equals(sourceId));
}

SensorRecord extractSensorRecord(
    const precording::log_event::EventRecord& eventRecord)
{
    std::optional<double> x, y, z;
    std::optional<chrono::TimePoint> time;
    for (int i = 0; i < eventRecord.params_size(); i++) {
        if (eventRecord.params(i).event() == "x") {
            x = std::stod(eventRecord.params(i).value());
        } else if (eventRecord.params(i).event() == "y") {
            y = std::stod(eventRecord.params(i).value());
        } else if (eventRecord.params(i).event() == "z") {
            z = std::stod(eventRecord.params(i).value());
        } else if (eventRecord.params(i).event() == "absTs") {
            int64_t milliseconds = std::stoll(eventRecord.params(i).value());
            time = chrono::TimePoint(
                std::chrono::milliseconds(milliseconds));
        }
    }
    REQUIRE(x && y && z && time,
            "event record with missed parameters");
    return SensorRecord{eventRecord.event(), *x, *y, *z, *time};
}

SensorRecords extractSensorRecords(std::vector<std::stringstream>& reports)
{
    SensorRecords sensorRecords;

    for (std::stringstream& report : reports) {
        maps::pb_stream2::Reader reader(&report);
        for (auto it = reader.begin(); it != reader.end(); ++it) {
            auto record = it->as<precording::record::Record>();
            if (record.HasExtension(precording::log_event::EVENT_RECORD)) {
                auto& eventRecord = record.GetExtension(precording::log_event::EVENT_RECORD);
                if (eventRecord.event() != SENSOR_TYPE_ACCELEROMETER
                    && eventRecord.event() != SENSOR_TYPE_GYROSCOPE)
                {
                    continue;
                }
                if (eventRecord.component() == OLD_SENSORS_VERSION_1
                    || eventRecord.component() == OLD_SENSORS_VERSION_2)
                {
                    // skip assignments with old sensor versions
                    return {};
                }
                if (eventRecord.component() != SENSORS_VERSION) {
                    continue;
                }
                sensorRecords.push_back(extractSensorRecord(eventRecord));
            }
        }
    }
    return sensorRecords;
}

} // anonymous namespace

SensorEvents extractSensors(std::vector<std::stringstream>& reports,
                            std::optional<chrono::TimePoint> minTime,
                            std::optional<chrono::TimePoint> maxTime)
{
    SensorRecords sensorRecords = extractSensorRecords(reports);

    std::sort(sensorRecords.begin(), sensorRecords.end(),
              [](const SensorRecord& lhs, const SensorRecord& rhs) {
                  return lhs.time < rhs.time;
              });

    pos_improvment::GyroscopeEvents gyroEvents;
    pos_improvment::AccelerometerEvents accEvents;

    for (const SensorRecord& sensorRecord : sensorRecords) {
        if ((minTime && sensorRecord.time < *minTime)
            || (maxTime && sensorRecord.time > *maxTime))
        {
            continue;
        }

        if (sensorRecord.type == SENSOR_TYPE_ACCELEROMETER) {
            accEvents.push_back(
                pos_improvment::createAccelerometerEvent(
                    sensorRecord.time,
                    sensorRecord.x,
                    sensorRecord.y,
                    sensorRecord.z));
        } else {
            gyroEvents.push_back(
                pos_improvment::createGyroscopeEvent(
                    sensorRecord.time,
                    sensorRecord.x,
                    sensorRecord.y,
                    sensorRecord.z));
        }
    }

    return {gyroEvents, accEvents};

}

SensorEvents loadAssignmentSensors(
    mds::Mds& mdsClient,
    pgpool3::Pool& pool,
    chrono::TimePoint minTime,
    chrono::TimePoint maxTime,
    std::string sourceId,
    db::TId assignmentId)
{
    std::vector<ugc::AssignmentRecordingReport> reports
        = loadReports(pool, sourceId, assignmentId);

    std::vector<std::stringstream> reportsStr;
    for (auto report : reports) {
        reportsStr.push_back(std::stringstream{});
        mdsClient.get(report.mdsKey(), reportsStr.back());
    }
    return extractSensors(reportsStr, minTime, maxTime);
}

SensorEvents loadRideSensors(
    mds::Mds& mdsClient,
    pgpool3::Pool& pool,
    chrono::TimePoint minTime,
    chrono::TimePoint maxTime,
    std::string sourceId)
{
    std::vector<rides::RideRecordingReport> reports
        = loadReports(pool, sourceId, minTime, maxTime);

    std::vector<std::stringstream> reportsStr;
    for (auto report : reports) {
        reportsStr.push_back(std::stringstream{});
        mdsClient.get(mds::Key(report.mdsGroupId(), report.mdsPath()),
                      reportsStr.back());
    }
    return extractSensors(reportsStr, minTime, maxTime);
}

} // namespace sensors_feature_positioner
