#include "utility.h"

#include <maps/libs/log8/include/log8.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/ride_utility.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/ride_inspector/lib/ride.h>

namespace maps::mrc::ride_correction {

namespace {

void updateDeletedIntervalsWithoutClientRideId(
    pgpool3::Pool& pool,
    const std::string& userId,
    const std::string& sourceId,
    chrono::TimePoint startTime,
    chrono::TimePoint endTime,
    const ugc_event_logger::UserInfo& userInfo,
    ugc_event_logger::Logger& ugcLogger)
{
    auto deletedIntervals =
        db::DeletedIntervalGateway{*pool.masterReadOnlyTransaction()}.load(
            db::table::DeletedInterval::userId == userId &&
            db::table::DeletedInterval::sourceId == sourceId &&
            db::table::DeletedInterval::clientRideId.isNull() &&
            db::table::DeletedInterval::startedAt <= endTime &&
            db::table::DeletedInterval::endedAt >= startTime);
    for (const auto& deletedInterval : deletedIntervals) {
        auto photos =
            db::FeatureGateway{*pool.masterReadOnlyTransaction()}.load(
                db::table::Feature::userId == deletedInterval.userId() &&
                db::table::Feature::sourceId == deletedInterval.sourceId() &&
                db::table::Feature::clientRideId.isNull() &&
                db::table::Feature::date.between(deletedInterval.startedAt(),
                                                 deletedInterval.endedAt()) &&
                !db::table::Feature::deletedByUser.is(true));
        for (auto& photo : photos) {
            photo.setDeletedByUser(true);
            ugcLogger.logEvent(
                userInfo,
                ugc_event_logger::Action::Delete,
                ugc_event_logger::Photo{.id = std::to_string(photo.id())}
            );
        }
        if (!photos.empty()) {
            auto txn = pool.masterWriteableTransaction();
            db::FeatureGateway{*txn}.update(photos, db::UpdateFeatureTxn::Yes);
            txn->commit();
            INFO() << "deleted photos: " << photos.size();
        }
    }
}

const auto getTimestamp = TOverloaded{
    [](chrono::TimePoint timestamp) { return timestamp; },
    [](const db::Feature& feature) { return feature.timestamp(); },
};

const auto lessTimestamp = [](const auto& lhs, const auto& rhs) {
    return getTimestamp(lhs) < getTimestamp(rhs);
};

void erasePhotosIfNotDeleted(const db::DeletedIntervals& deletedIntervals,
                             db::Features& photos)
{
    std::sort(photos.begin(), photos.end(), lessTimestamp);
    for (const auto& deletedInterval : deletedIntervals) {
        auto first = std::lower_bound(photos.begin(),
                                      photos.end(),
                                      deletedInterval.startedAt(),
                                      lessTimestamp);
        auto last = std::upper_bound(
            first, photos.end(), deletedInterval.endedAt(), lessTimestamp);
        std::for_each(first, last, [](db::Feature& photo) {
            photo.setDeletedByUser(true);
        });
    }
    std::erase_if(photos, [](const db::Feature& photo) {
        return !photo.deletedByUser().value_or(false);
    });
}

void updateDeletedIntervals(pgpool3::Pool& pool,
                            const std::string& userId,
                            const std::string& sourceId,
                            const std::optional<std::string>& clientRideId,
                            chrono::TimePoint startTime,
                            chrono::TimePoint endTime,
                            const ugc_event_logger::UserInfo& userInfo,
                            ugc_event_logger::Logger& ugcLogger)
{
    if (!clientRideId) {
        return updateDeletedIntervalsWithoutClientRideId(
            pool, userId, sourceId, startTime, endTime, userInfo, ugcLogger);
    }

    auto deletedIntervals =
        db::DeletedIntervalGateway{*pool.masterReadOnlyTransaction()}.load(
            db::table::DeletedInterval::clientRideId == *clientRideId);
    requireEquals(deletedIntervals, userId, sourceId, clientRideId);
    auto photos = db::FeatureGateway{*pool.masterReadOnlyTransaction()}.load(
        db::table::Feature::clientRideId == *clientRideId &&
        !db::table::Feature::deletedByUser.is(true));
    requireEquals(photos, userId, sourceId, clientRideId);
    erasePhotosIfNotDeleted(deletedIntervals, photos);
    if (!photos.empty()) {
        for (const auto& photo : photos) {
            ugcLogger.logEvent(
                userInfo,
                ugc_event_logger::Action::Delete,
                ugc_event_logger::Photo{.id = std::to_string(photo.id())});
        }
        auto txn = pool.masterWriteableTransaction();
        db::FeatureGateway{*txn}.update(photos, db::UpdateFeatureTxn::Yes);
        txn->commit();
        INFO() << "deleted photos: " << photos.size();
    }
}

void setShowAuthorship(pgpool3::Pool& pool,
                       const std::string& userId,
                       const std::string& sourceId,
                       const std::optional<std::string>& clientRideId,
                       chrono::TimePoint startTime,
                       chrono::TimePoint endTime,
                       bool showAuthorship,
                       const ugc_event_logger::UserInfo& userInfo,
                       ugc_event_logger::Logger& ugcLogger)
{
    auto allOf =
        makeFeatureFilter(userId, sourceId, clientRideId, startTime, endTime);
    if (showAuthorship) {
        allOf.add(!db::table::Feature::showAuthorship.is(true));
    }
    else {
        allOf.add(db::table::Feature::showAuthorship.is(true));
    }
    auto photos =
        db::FeatureGateway{*pool.masterReadOnlyTransaction()}.load(allOf);
    for (auto& photo : photos) {
        requireEquals(photo, userId, sourceId, clientRideId);
        photo.setShowAuthorship(showAuthorship);
        ugcLogger.logEvent(
                userInfo,
                ugc_event_logger::Action::Update,
                ugc_event_logger::Photo{.id = std::to_string(photo.id()),
                                        .showAuthorship = showAuthorship}
            );
    }
    if (!photos.empty()) {
        auto txn = pool.masterWriteableTransaction();
        db::FeatureGateway{*txn}.update(photos, db::UpdateFeatureTxn::Yes);
        txn->commit();
        INFO() << (showAuthorship ? "show" : "hide")
               << " authorship photos: " << photos.size();
    }
}

}  // namespace

void updateRides(pgpool3::Pool& pool,
                 const std::string& userId,
                 const std::string& sourceId,
                 const std::optional<std::string>& clientRideId,
                 chrono::TimePoint startTime,
                 chrono::TimePoint endTime,
                 std::optional<bool> showAuthorship,
                 const ugc_event_logger::UserInfo& userInfo,
                 ugc_event_logger::Logger& ugcLogger)
{
    updateDeletedIntervals(pool,
                           userId,
                           sourceId,
                           clientRideId,
                           startTime,
                           endTime,
                           userInfo,
                           ugcLogger);
    if (showAuthorship.has_value()) {
        setShowAuthorship(pool,
                          userId,
                          sourceId,
                          clientRideId,
                          startTime,
                          endTime,
                          showAuthorship.value(),
                          userInfo,
                          ugcLogger);
    }
    ride_inspector::updateRides(pool,
                                userId,
                                sourceId,
                                clientRideId,
                                startTime,
                                endTime);
}

sql_chemistry::FiltersCollection makeFeatureFilter(const std::string& userId,
                const std::string& sourceId,
                const std::optional<std::string>& clientRideId,
                chrono::TimePoint startTime,
                chrono::TimePoint endTime)
{
    auto result =
        sql_chemistry::FiltersCollection{sql_chemistry::op::Logical::And};
    if (clientRideId) {
        result.add(db::table::Feature::clientRideId == *clientRideId);
    }
    else {
        result.add(db::table::Feature::userId == userId);
        result.add(db::table::Feature::sourceId == sourceId);
        result.add(db::table::Feature::clientRideId.isNull());
        result.add(db::table::Feature::date.between(startTime, endTime));
    }
    return result;
}

}  // namespace maps::mrc::ride_correction
