#include <yandex/maps/wiki/diffalert/storage/results_viewer.h>
#include "magic_strings.h"

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

#include <string>

namespace maps {
namespace wiki {
namespace diffalert {

namespace {

std::string whereClause(
    TaskId taskId,
    const StoredMessagesFilter& filter,
    pqxx::transaction_base& txn)
{
    std::string clause = columns::TASK_ID + " = " + std::to_string(taskId);

    if (filter.geomWkb) {
        clause += " AND ST_Intersects(" + columns::THE_GEOM + ',' +
            "ST_GeomFromWKB('" + txn.esc_raw(*filter.geomWkb) + "'," +
            std::to_string(MERCATOR_SRID) + "))";
    }
    if (filter.majorPriority) {
        clause += " AND " + columns::MAJOR_PRIORITY + " = " +
            std::to_string(*filter.majorPriority);
    }
    if (!filter.categoryIds.empty()) {
        clause += " AND " + columns::CATEGORY_ID + " IN (";
        clause += common::join(
            filter.categoryIds,
            [&txn](const std::string& s) { return txn.quote(s); },
            ',');
        clause += ')';
    }
    if (filter.description) {
        clause += " AND " + columns::DESCRIPTION + " = " +
            txn.quote(*filter.description);
    }
    if (filter.regionPriority) {
        clause += " AND " + columns::REGION_PRIORITY + " = " +
            std::to_string(*filter.regionPriority);
    }
    if (filter.postponed) {
        clause += " AND " + columns::POSTPONED + " = " +
            (*filter.postponed ? "TRUE" : "FALSE");
    }
    if (filter.excludeInspectedBy) {
        clause += " AND " + columns::INSPECTED_BY + " != " +
            std::to_string(*filter.excludeInspectedBy);
    }
    return clause;
}

} // namespace

ResultsViewer::ResultsViewer(TaskId taskId, pqxx::transaction_base& txn)
    : taskId_(taskId)
    , txn_(txn)
{ }

size_t ResultsViewer::messageCount(const StoredMessagesFilter& filter)
{
    std::string query =
        "SELECT count(*) FROM " + tables::MESSAGES + join::WITH_ATTRIBUTES +
        " WHERE " + whereClause(taskId_, filter, txn_);
    return txn_.exec(query).at(0).at(0).as<size_t>();
}

MessageStatistics ResultsViewer::statistics(
    const StoredMessagesFilter& filter)
{
    std::string query =
        "SELECT " +
                columns::REGION_PRIORITY + ',' +
                columns::MAJOR_PRIORITY + ',' +
                columns::CATEGORY_ID + ',' +
                columns::DESCRIPTION + ',' +
                "count(*) as total_count, "
                "count(inspected_at) as inspected_count" +
            " FROM " + tables::MESSAGES + join::WITH_ATTRIBUTES +
            " WHERE " + whereClause(taskId_, filter, txn_) +
            " GROUP BY " +
                columns::REGION_PRIORITY + ',' +
                columns::MAJOR_PRIORITY + ',' +
                columns::CATEGORY_ID + ',' +
                columns::DESCRIPTION;

    auto result = txn_.exec(query);

    MessageStatistics stats;
    stats.reserve(result.size());
    for (const auto& row : result) {
        stats.emplace_back(MessageStatisticsItem{
            row[columns::REGION_PRIORITY].as<uint32_t>(),
            row[columns::MAJOR_PRIORITY].as<uint32_t>(),
            row[columns::CATEGORY_ID].as<std::string>(),
            row[columns::DESCRIPTION].as<std::string>(),
            row[columns::TOTAL_COUNT].as<size_t>(),
            row[columns::INSPECTED_COUNT].as<size_t>()
        });
    }
    return stats;
}

std::vector<StoredMessageId> ResultsViewer::messageIds(
    const StoredMessagesFilter& filter)
{
    auto query = messagesQuery(filter, columns::ID);

    std::vector<StoredMessageId> messageIds;
    for (const auto& row : txn_.exec(query)) {
        messageIds.push_back(row[columns::ID].as<StoredMessageId>());
    }
    return messageIds;
}

StoredMessages ResultsViewer::messages(
    const StoredMessagesFilter& filter,
    SortKind sortKind,
    size_t offset,
    size_t limit)
{
    auto query = messagesQuery(filter, columns::MESSAGE_COLUMNS);

    query += " ORDER BY ";

    switch (sortKind) {
    case SortKind::ByName:
        query +=
            columns::MAJOR_PRIORITY + ',' +
            columns::MINOR_PRIORITY + ',' +
            columns::HAS_OWN_NAME + " DESC," +
            columns::OBJECT_LABEL + ',' +
            columns::ID;
        break;
    case SortKind::BySize:
        query +=
            columns::MAJOR_PRIORITY + ',' +
            columns::MINOR_PRIORITY + ',' +
            columns::SORT_PRIORITY + ',' +
            columns::ID;
        break;
    }

    query +=
        " OFFSET " + std::to_string(offset) +
        " LIMIT " + std::to_string(limit);

    StoredMessages messages;
    for (const auto& row : txn_.exec(query)) {
        messages.emplace_back(row);
    }
    return messages;
}

std::optional<StoredMessage> ResultsViewer::message(StoredMessageId messageId)
{
    auto query = "SELECT " + columns::MESSAGE_COLUMNS +
        " FROM " + tables::MESSAGES + join::WITH_ATTRIBUTES +
            " WHERE " + columns::ID + "=" + std::to_string(messageId);

    auto result = txn_.exec(query);
    if (result.empty()) {
        return std::nullopt;
    }
    return StoredMessage(result.front());
}

std::string ResultsViewer::messagesQuery(
    const StoredMessagesFilter& filter,
    const std::string& columns) const
{
    return
        "SELECT " + columns +
        " FROM " + tables::MESSAGES + join::WITH_ATTRIBUTES +
            " WHERE " + whereClause(taskId_, filter, txn_);
}

} // namespace diffalert
} // namespace wiki
} // namespace maps
