#include <yandex/maps/wiki/social/published_commits.h>

#include "magic_strings.h"

#include <yandex/maps/wiki/common/batch.h>
#include <yandex/maps/wiki/common/pg_utils.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <string>
#include <type_traits>

using namespace std::string_literals;

namespace maps::wiki::social {

namespace {

const size_t PUBLISHED_COMMITS_BATCH_SIZE = 1000;

std::set<std::string> defaultHandlers() {
    return {
        commits_handlers::RELEASES_NOTIFICATION,
        commits_handlers::NOTIFICATIONS_DISPATCHER
    };
}

}

template <typename Container>
std::string PublishedCommits::containerValues(const Container& data) const {
    return common::join(
        data,
        [&](const auto& value) {
            if constexpr (std::is_arithmetic_v<typename Container::value_type>) {
                return '(' + std::to_string(value) + ')';
            } else {
                return '(' + txn_.quote(value) + ')';
            }
        },
        ','
    );
}

void PublishedCommits::push(const TIds& commitIds) {
    push(commitIds, defaultHandlers());
}

void PublishedCommits::push(
    const TIds& commitIds,
    const std::set<std::string>& handlers
) {
    std::string handlersValues = containerValues(handlers);

    common::applyBatchOp(
        commitIds,
        PUBLISHED_COMMITS_BATCH_SIZE,
        [&](const TIds& batch) {
            auto commitIdsValues = containerValues(batch);
            auto query =
                "INSERT INTO "s + sql::table::PUBLISHED_COMMITS +
                "  ("s + sql::col::COMMIT_ID + ',' + sql::col::HANDLER + ')' +
                " SELECT * FROM "s +
                "  ( VALUES "s + commitIdsValues + " ) AS ids ("s + sql::col::COMMIT_ID + "),"s +
                "  ( VALUES "s + handlersValues + ") AS handlers ("s + sql::col::HANDLER + ')';

            txn_.exec(query);
        }
    );
}

TIds PublishedCommits::getUnprocessedIds(const std::string& handler) {
    auto query =
        "SELECT "s + sql::col::COMMIT_ID +
        " FROM "s + sql::table::PUBLISHED_COMMITS +
        " WHERE "s + sql::col::HANDLER + '=' + txn_.quote(handler) +
        "  AND "s + sql::col::PROCESSED + '=' + sql::value::FALSE +
        " ORDER BY 1 FOR UPDATE"s;

    TIds publishedCommitIds;
    for (const auto& row : txn_.exec(query)) {
        publishedCommitIds.emplace(row[0].as<TId>());
    }

    return publishedCommitIds;
}

void PublishedCommits::process(const TIds& commitIds, const std::string& handler) {
    common::applyBatchOp(
        commitIds,
        PUBLISHED_COMMITS_BATCH_SIZE,
        [&](const TIds& batch) {
            auto query =
                "UPDATE "s + sql::table::PUBLISHED_COMMITS +
                " SET "s + sql::col::PROCESSED + '=' + sql::value::TRUE +
                " WHERE "s + common::whereClause(sql::col::COMMIT_ID, batch) +
                "  AND "s + sql::col::HANDLER + '=' + txn_.quote(handler);

            txn_.exec(query);
        }
    );
}
}  // namespace maps::wiki::social
