#include "utility.h"

#include <maps/libs/common/include/make_batches.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/parallel_for_each.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/ride_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/takeout_data_erasure_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/ride_inspector/lib/ride.h>

#include <set>

namespace maps::mrc::takeout_data_erasure {

namespace {

void eraseFeatures(pgpool3::Pool& pool, db::TIds&& featureIds)
{
    static constexpr auto BATCH_SIZE = size_t{1000};
    static constexpr auto MAX_THREADS = 4;

    std::sort(featureIds.begin(), featureIds.end());
    auto batches = maps::common::makeBatches(featureIds, BATCH_SIZE);
    auto counter = size_t{0};
    common::parallelForEach<MAX_THREADS>(
        batches.begin(),
        batches.end(),
        [&](std::mutex& guard, const auto& batch) {
            auto features = db::FeatureGateway{*pool.slaveTransaction()}.load(
                db::table::Feature::id.in({batch.begin(), batch.end()}) and
                not db::table::Feature::gdprDeleted.is(true));

            for (auto& feature : features) {
                feature.setGdprDeleted(true);
            }

            auto txn = pool.masterWriteableTransaction();
            db::FeatureGateway{*txn}.update(features, db::UpdateFeatureTxn::Yes);
            txn->commit();

            std::lock_guard lock{guard};
            counter += features.size();
            INFO() << db::table::Feature::name_ << ": " << counter
                   << " erasured";
        });
}

void updateRides(pgpool3::Pool& pool, db::TIds&& rideIds)
{
    for (auto rideId : rideIds) {
        auto ride = db::RideGateway{*pool.slaveTransaction()}.loadById(rideId);
        ride_inspector::updateRides(pool,
                                    ride.userId(),
                                    ride.sourceId(),
                                    ride.clientId(),
                                    ride.startTime(),
                                    ride.endTime());
    }
}

}  // namespace

void erasure(pgpool3::Pool& pool, db::TId takeoutDataErasureId)
{
    INFO() << "Start takeout data erasure " << takeoutDataErasureId;

    auto takeoutDataErasure =
        db::TakeoutDataErasureGateway{*pool.slaveTransaction()}.loadById(
            takeoutDataErasureId);
    if (takeoutDataErasure.finishedAt().has_value()) {
        INFO() << "Takeout data erasure " << takeoutDataErasureId
               << " is already done";
        return;
    }

    eraseFeatures(
        pool,
        db::FeatureGateway{*pool.slaveTransaction()}.loadIds(
            db::table::Feature::userId == takeoutDataErasure.userId() and
            db::table::Feature::date <= takeoutDataErasure.requestedAt() and
            not db::table::Feature::gdprDeleted.is(true)));

    updateRides(
        pool,
        db::RideGateway{*pool.slaveTransaction()}.loadIds(
            db::table::Ride::userId == takeoutDataErasure.userId() and
            db::table::Ride::startTime <= takeoutDataErasure.requestedAt() and
            not db::table::Ride::isDeleted));

    takeoutDataErasure.setFinishedAt(chrono::TimePoint::clock::now());

    auto txn = pool.masterWriteableTransaction();
    db::TakeoutDataErasureGateway{*txn}.update(takeoutDataErasure);
    txn->commit();

    INFO() << "Done with takeout data erasure " << takeoutDataErasureId;
}

}  // namespace maps::mrc::takeout_data_erasure
