#include <yandex/maps/wiki/diffalert/storage/stored_message.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 <utility>

namespace maps {
namespace wiki {
namespace diffalert {

namespace {
const size_t MESSAGE_BATCH_SIZE = 1000;
} // name

StoredMessage::StoredMessage(
        const Message& message,
        std::string categoryId,
        std::string objectLabel,
        HasOwnName hasOwnName,
        const Envelope& envelope)
    : id_(0)
    , objectId_(message.objectId())
    , priority_(message.priority())
    , description_(message.description())
    , categoryId_(std::move(categoryId))
    , objectLabel_(std::move(objectLabel))
    , hasOwnName_(hasOwnName)
    , envelope_(envelope)
    , inspectedBy_(0)
    , postponed_(false)
{
}

StoredMessage::StoredMessage(
        const pqxx::row& row)
    : id_(row[columns::ID].as<StoredMessageId>())
    , objectId_(row[columns::OBJECT_ID].as<TId>())
    , priority_{
        row[columns::MAJOR_PRIORITY].as<uint32_t>(),
        row[columns::MINOR_PRIORITY].as<uint32_t>(),
        row[columns::SORT_PRIORITY].as<double>()}
    , description_(row[columns::DESCRIPTION].as<std::string>())
    , categoryId_(row[columns::CATEGORY_ID].as<std::string>())
    , objectLabel_(row[columns::OBJECT_LABEL].as<std::string>())
    , hasOwnName_ (row[columns::HAS_OWN_NAME].as<bool>()
        ? HasOwnName::Yes
        : HasOwnName::No)
    , inspectedBy_(row[columns::INSPECTED_BY].as<TUId>())
    , postponed_(row[columns::POSTPONED].as<bool>())
{
    if (!row[columns::THE_GEOM].is_null()) {
        envelope_ = *Geom(pqxx::binarystring(row[columns::THE_GEOM]).str())
            ->getEnvelopeInternal();
    }
    if (!row[columns::INSPECTED_AT].is_null()) {
        inspectedAt_ = row[columns::INSPECTED_AT].as<std::string>();
    }
}

StoredMessages StoredMessage::markAsInspected(
        pqxx::transaction_base& txn,
        const std::vector<StoredMessageId>& messageIds,
        TUId uid)
{
    std::set<StoredMessageId> affectedMessageIds;
    common::applyBatchOp(
        messageIds,
        MESSAGE_BATCH_SIZE,
        [&](const std::vector<StoredMessageId>& batchMessageIds) {
            auto query =
                "WITH affected AS "
                "   (SELECT " + columns::TASK_ID + " as tid," + columns::OBJECT_ID + " as oid"
                "    FROM " + tables::MESSAGES +
                "    WHERE " + common::whereClause(columns::ID, batchMessageIds) + ")"
                " SELECT " + columns::ID +
                " FROM " + tables::MESSAGES + " m, affected"
                " WHERE " + columns::INSPECTED_BY + " = 0"
                "   AND " + columns::TASK_ID + " = tid"
                "   AND " + columns::OBJECT_ID + " = oid";

            for (const auto& row : txn.exec(query)) {
                affectedMessageIds.insert(row[0].as<StoredMessageId>());
            }
        });

    StoredMessages messages;
    common::applyBatchOp(
        affectedMessageIds,
        MESSAGE_BATCH_SIZE,
        [&](const std::set<StoredMessageId>& batchMessageIds) {
            auto query =
                "SELECT " + columns::ID +
                " FROM " + tables::MESSAGES +
                " WHERE " + common::whereClause(columns::ID, batchMessageIds) +
                " ORDER BY 1 FOR UPDATE;"
                "UPDATE " + tables::MESSAGES + " m"
                " SET " +
                    columns::INSPECTED_BY + " = " + std::to_string(uid) + "," +
                    columns::INSPECTED_AT + " = NOW()" +
                " FROM " + tables::MESSAGE_ATTRIBUTES + " a"
                " WHERE m." + columns::ATTRIBUTES_ID + " = a." + columns::ATTRIBUTES_ID +
                "   AND " + common::whereClause(columns::ID, batchMessageIds) +
                " RETURNING " + columns::MESSAGE_COLUMNS;

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

StoredMessage StoredMessage::postpone(
        pqxx::transaction_base& txn,
        StoredMessageId messageId,
        PostponeAction action)
{
    auto query =
        "UPDATE " + tables::MESSAGES + " m SET " +
        columns::POSTPONED + " = " + (action == PostponeAction::Postpone ? "TRUE" : "FALSE") +
        " FROM " + tables::MESSAGE_ATTRIBUTES + " a" +
        " WHERE m." + columns::ATTRIBUTES_ID + " = a." + columns::ATTRIBUTES_ID +
        " AND " + columns::ID + " = " + std::to_string(messageId) +
        " RETURNING " + columns::MESSAGE_COLUMNS;

    auto result = txn.exec(query);
    ASSERT(!result.empty());
    return StoredMessage(result.front());
}

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