#include <maps/wikimap/mapspro/services/gdpr/src/lib/utils.h>
#include <maps/wikimap/mapspro/services/gdpr/src/lib/sql_strings.h>

#include <maps/wikimap/mapspro/libs/social/include/yandex/maps/wiki/social/common.h>

namespace maps::wiki::gdpr {

namespace impl {

namespace {

const std::string CREATED_BY = "created_by";
const std::string MODIFIED_BY = "modified_by";
const std::string RESOLVED_BY = "resolved_by";
const std::string CLOSED_BY = "closed_by";

bool hasUserData(
    pqxx::transaction_base& txn,
    const std::string& table,
    const std::string& column,
    TUid uid)
{
    auto query =
        "SELECT FROM " + table +
        " WHERE " + column + " = " + std::to_string(uid) +
        " LIMIT 1";
    return !txn.exec(query).empty();
}

size_t countUserData(
    pqxx::transaction_base& txn,
    const std::string& table,
    const std::string& column,
    TUid uid)
{
    auto query =
        "SELECT COUNT(1) FROM " + table +
        " WHERE " + column + " = " + std::to_string(uid);
    return txn.exec(query)[0][0].as<size_t>();
}

} // namespace

bool hasEdits(
    pqxx::transaction_base& txnCore,
    TUid uid)
{
    return hasUserData(
        txnCore, sql::table::REVISION_COMMIT, CREATED_BY, uid);
}

size_t countEdits(
    pqxx::transaction_base& txnCore,
    TUid uid)
{
    return countUserData(
        txnCore, sql::table::REVISION_COMMIT, CREATED_BY, uid);
}

bool hasSocialEvents(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return hasUserData(
        txnSocial, sql::table::SOCIAL_COMMIT_EVENT, CREATED_BY, uid);
}

size_t countSocialEvents(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(
        txnSocial, sql::table::SOCIAL_COMMIT_EVENT, CREATED_BY, uid);
}

bool hasSocialComments(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return hasUserData(
        txnSocial, sql::table::SOCIAL_COMMENT, CREATED_BY, uid);
}

size_t countSocialComments(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(
        txnSocial, sql::table::SOCIAL_COMMENT, CREATED_BY, uid);
}

bool hasSocialFeedbackEvents(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return hasUserData(
        txnSocial, sql::table::SOCIAL_FEEDBACK_EVENT, CREATED_BY, uid);
}

size_t countSocialFeedbackEvents(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(
        txnSocial, sql::table::SOCIAL_FEEDBACK_EVENT, CREATED_BY, uid);
}

bool hasSocialFeedbackHistory(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return hasUserData(
        txnSocial, sql::table::SOCIAL_FEEDBACK_HISTORY, MODIFIED_BY, uid);
}

size_t countSocialFeedbackHistory(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(
        txnSocial, sql::table::SOCIAL_FEEDBACK_HISTORY, MODIFIED_BY, uid);
}

bool hasSocialFeedbackTasks(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return hasUserData(
        txnSocial, sql::table::SOCIAL_FEEDBACK_TASK_OUTGOING_CLOSED, RESOLVED_BY, uid);
}

size_t countSocialFeedbackTasks(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(
        txnSocial, sql::table::SOCIAL_FEEDBACK_TASK_OUTGOING_CLOSED, RESOLVED_BY, uid);
}

bool hasSocialModerationTasks(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return hasUserData(txnSocial, sql::table::SOCIAL_TASK, RESOLVED_BY, uid)
        || hasUserData(txnSocial, sql::table::SOCIAL_TASK_CLOSED, CLOSED_BY, uid);
}

size_t countSocialModerationTasksResolved(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(txnSocial, sql::table::SOCIAL_TASK, RESOLVED_BY, uid);
}

size_t countSocialModerationTasksClosed(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(txnSocial, sql::table::SOCIAL_TASK_CLOSED, CLOSED_BY, uid);
}

bool hasSpravTasks(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return hasUserData(
        txnSocial, sql::table::SPRAV_TASKS, CREATED_BY, uid);
}

size_t countSpravTasks(
    pqxx::transaction_base& txnSocial,
    TUid uid)
{
    return countUserData(
        txnSocial, sql::table::SPRAV_TASKS, CREATED_BY, uid);
}

} // namespace impl

std::map<std::string, size_t> getCounts(
    pgpool3::Pool& core,
    pgpool3::Pool& social,
    TUid uid)
{
    std::map<std::string, size_t> result;
    auto add = [&](const auto& key, size_t count) {
        if (count) {
            result.emplace(key, count);
        }
    };

#define countX(txn, X) add(#X, impl::count##X(txn, uid))
    {
        auto txn = core.slaveTransaction();
        countX(*txn, Edits);
    }
    {
        auto txn = social.slaveTransaction();
        countX(*txn, SocialEvents);
        countX(*txn, SocialComments);
        countX(*txn, SocialFeedbackEvents);
        countX(*txn, SocialFeedbackHistory);
        countX(*txn, SocialFeedbackTasks);
        countX(*txn, SocialModerationTasksResolved);
        countX(*txn, SocialModerationTasksClosed);
        countX(*txn, SpravTasks);
    }
#undef countX

    return result;
}

TakeoutState currentTakeoutState(
    pgpool3::Pool& core,
    pgpool3::Pool& social,
    TUid uid)
{
    auto txnCore = core.slaveTransaction();
    auto data = currentTakeoutData(*txnCore, uid);
    if (data) {
        return TakeoutState::DeleteInProgress;
    }

    auto hasUserData = [&] {
        if (impl::hasEdits(*txnCore, uid)) {
            return true;
        }
        auto txnSocial = social.slaveTransaction();
        return impl::hasSocialEvents(*txnSocial, uid)
            || impl::hasSocialComments(*txnSocial, uid)
            || impl::hasSocialFeedbackEvents(*txnSocial, uid)
            || impl::hasSocialFeedbackTasks(*txnSocial, uid)
            || impl::hasSocialFeedbackHistory(*txnSocial, uid)
            || impl::hasSocialModerationTasks(*txnSocial, uid)
            || impl::hasSpravTasks(*txnSocial, uid);
    };

    return hasUserData()
        ? TakeoutState::ReadyToDelete
        : TakeoutState::Empty;
}

std::optional<TakeoutData> currentTakeoutData(
    pqxx::transaction_base& txnCore,
    TUid uid)
{
    auto query =
        "SELECT *"
        " FROM " + sql::table::GDPR_TAKEOUT +
        " WHERE " + sql::column::UID + "=" + std::to_string(uid) +
        " AND " + sql::column::COMPLETED_AT + " IS NULL";
    auto rows = txnCore.exec(query);
    if (rows.empty()) {
        return std::nullopt;
    }
    return TakeoutData(rows[0]);
}

std::vector<TakeoutData> getAllTakeoutData(
    pqxx::transaction_base& txnCore,
    TUid uid)
{
    auto query =
        "SELECT *"
        " FROM " + sql::table::GDPR_TAKEOUT +
        " WHERE " + sql::column::UID + "=" + std::to_string(uid) +
        " ORDER BY " + sql::column::TAKEOUT_ID;
    auto rows = txnCore.exec(query);

    std::vector<TakeoutData> result;
    result.reserve(rows.size());
    for (const auto& row : rows) {
        result.emplace_back(row);
    }
    return result;
}

TakeoutData createTakeoutData(
    pqxx::transaction_base& txnCore,
    TUid uid,
    const std::string& requestId)
{
    auto query =
        "INSERT INTO " + sql::table::GDPR_TAKEOUT +
        " (" + sql::column::UID + "," + sql::column::REQUEST_ID +
        " ) VALUES (" +
        std::to_string(uid) + "," + txnCore.quote(requestId) +
        ") RETURNING *";
    auto rows = txnCore.exec(query);
    ASSERT(rows.size() == 1);
    return TakeoutData(rows[0]);
}

std::vector<TakeoutData> getTakeoutDataWithThreshold(
    pqxx::transaction_base& txnCore,
    std::chrono::days threshold)
{
    auto query =
        "SELECT *"
        " FROM " + sql::table::GDPR_TAKEOUT +
        " WHERE " + sql::column::REQUESTED_AT + "< NOW()"
            " - interval '" + std::to_string(threshold.count()) + " days'"
        " AND " + sql::column::COMPLETED_AT + " IS NULL"
        " ORDER BY " + sql::column::TAKEOUT_ID;
    auto rows = txnCore.exec(query);

    std::vector<TakeoutData> result;
    result.reserve(rows.size());
    for (const auto& row : rows) {
        result.emplace_back(row);
    }
    return result;
}

} // namespace maps::wiki::gdpr
