#include <yandex/maps/wiki/editor_takeout/editor_takeout.h>

#include <maps/libs/json/include/builder.h>
#include <maps/libs/json/include/value.h>

using namespace std::string_literals;

namespace maps::wiki::editor_takeout {

namespace {

const uint64_t BATCH_SIZE = 1000;

namespace sql::col {

const auto ATTRIBUTES = "attributes"s;
const auto CREATED = "created"s;
const auto CREATED_AT = "created_at"s;
const auto DATA = "data"s;
const auto OBJECT_ID = "object_id"s;
const auto TYPE = "type"s;

} // namespace sql::col

const auto ATTR_NAME_ACTION = "action"s;
const auto ATTR_NAME_PREFIX_EDIT_NOTES = "edit_notes:"s;
const auto ATTR_NAME_PREFIX_PRIMARY_OBJECT = "primary_object:"s;

const std::vector<std::string> EDIT_NOTES_PREFIXES = {
    "created"s,
    "deleted"s,
    "reverted"s,
    "modified-attributes"s,
    "modified-category"s,
    "modified-geometry"s,
    "modified-relations"s
};

const auto JSON_ACTION = "action"s;
const auto JSON_DATE_TIME = "dateTime"s;
const auto JSON_OBJECT_ID = "objectId"s;
const auto JSON_TEXT = "text"s;
const auto JSON_TYPE = "type"s;

struct EditData
{
    std::string primaryObjectId;
    std::string action;
    std::string dateTime;
};

struct MessageData
{
    std::string objectId;
    std::string type;
    std::string dateTime;
    std::string text;
};

std::string sqlQueryEdits(TId uid)
{
    return
        "SELECT "s +
            sql::col::CREATED + ", "s +
            "hstore_to_json("s + sql::col::ATTRIBUTES + ") as "s + sql::col::ATTRIBUTES +
        " FROM revision.commit "s +
        " WHERE created_by = "s + std::to_string(uid) +
        " ORDER BY "s + sql::col::CREATED;
}

std::string sqlQueryMessages(TId uid)
{
    return
        "SELECT "s +
            sql::col::OBJECT_ID + ", "s +
            sql::col::TYPE + ", "s +
            sql::col::CREATED_AT + ", "s +
            sql::col::DATA +
        " FROM social.comment "s +
        " WHERE created_by = "s + std::to_string(uid) +
        " ORDER BY "s + sql::col::CREATED_AT;
}

void uploadEditsBatch(
    const std::vector<EditData>& batch,
    size_t batchIndex,
    UploadToTakeout uploadCallback)
{
    json::Builder resultBuilder;
    resultBuilder << [&](json::ArrayBuilder editsBuilder) {
        for (const auto& editData : batch) {
            editsBuilder << [&](json::ObjectBuilder editBuilder) {
                if (!editData.primaryObjectId.empty()) {
                    editBuilder[JSON_OBJECT_ID] = editData.primaryObjectId;
                }
                if (!editData.action.empty()) {
                    editBuilder[JSON_ACTION] = editData.action;
                }
                if (!editData.dateTime.empty()) {
                    editBuilder[JSON_DATE_TIME] = editData.dateTime;
                }
            };
        }
    };
    uploadCallback(
        "edits_" + std::to_string(batchIndex) + ".json",
        resultBuilder.str());
}

void uploadMessagesBatch(
    const std::vector<MessageData>& batch,
    size_t batchIndex,
    UploadToTakeout uploadCallback)
{
    json::Builder resultBuilder;
    resultBuilder << [&](json::ArrayBuilder messagesBuilder) {
        for (const auto& messageData : batch) {
            messagesBuilder << [&](json::ObjectBuilder messageBuilder) {
                if (!messageData.objectId.empty()) {
                    messageBuilder[JSON_OBJECT_ID] = messageData.objectId;
                }
                if (!messageData.type.empty()) {
                    messageBuilder[JSON_TYPE] = messageData.type;
                }
                if (!messageData.dateTime.empty()) {
                    messageBuilder[JSON_DATE_TIME] = messageData.dateTime;
                }
                if (!messageData.text.empty()) {
                    messageBuilder[JSON_TEXT] = messageData.text;
                }
            };
        }
    };
    uploadCallback(
        "messages_" + std::to_string(batchIndex) + ".json",
        resultBuilder.str());
}

} // namespace

void
generateTakeoutEdits(
    pgpool3::Pool& coreLongReadPool,
    TId uid,
    UploadToTakeout uploadCallback)
{
    auto txnHandle = coreLongReadPool.slaveTransaction();
    std::vector<EditData> batch;
    size_t batchIndex = 0;

    for (const auto& row : txnHandle->exec(sqlQueryEdits(uid))) {
        if (batch.size() >= BATCH_SIZE) {
            ++batchIndex;
            uploadEditsBatch(batch, batchIndex, uploadCallback);
            batch.clear();
        }
        if (row[sql::col::ATTRIBUTES].is_null()) {
            continue;
        }

        EditData data;
        auto attributes = json::Value::fromString(row[sql::col::ATTRIBUTES].as<std::string>());

        data.dateTime = row[sql::col::CREATED].as<std::string>();
        for (const auto& field : attributes.fields()) {
            if (field.starts_with(ATTR_NAME_PREFIX_PRIMARY_OBJECT)) {
                data.primaryObjectId = field.substr(ATTR_NAME_PREFIX_PRIMARY_OBJECT.length());
            }
        }
        auto primaryObjectKey = ATTR_NAME_PREFIX_EDIT_NOTES + data.primaryObjectId;
        if (attributes.hasField(primaryObjectKey)) {
            auto editNotes = attributes[primaryObjectKey].toString();
            for (const auto& prefix : EDIT_NOTES_PREFIXES) {
                if (editNotes.starts_with(prefix)) {
                    data.action = prefix;
                    break;
                }
            }
        }
        if (data.action.empty() && attributes.hasField(ATTR_NAME_ACTION)) {
            data.action = attributes[ATTR_NAME_ACTION].toString();
        }
        batch.push_back(std::move(data));
    }
    if (!batch.empty()) {
        ++batchIndex;
        uploadEditsBatch(batch, batchIndex, uploadCallback);
    }
}

void
generateTakeoutMessages(
    pgpool3::Pool& socialPool,
    TId uid,
    UploadToTakeout uploadCallback)
{
    auto txnHandle = socialPool.masterReadOnlyTransaction();
    std::vector<MessageData> batch;
    size_t batchIndex = 0;

    for (const auto& row : txnHandle->exec(sqlQueryMessages(uid))) {
        if (batch.size() >= BATCH_SIZE) {
            ++batchIndex;
            uploadMessagesBatch(batch, batchIndex, uploadCallback);
            batch.clear();
        }

        MessageData data;
        data.type = row[sql::col::TYPE].as<std::string>();
        data.dateTime = row[sql::col::CREATED_AT].as<std::string>();
        if (row[sql::col::OBJECT_ID].as<TId>()) {
            data.objectId = row[sql::col::OBJECT_ID].as<std::string>();
        }
        if (!row[sql::col::DATA].is_null()) {
            data.text = row[sql::col::DATA].as<std::string>();
        }
        batch.push_back(std::move(data));
    }
    if (!batch.empty()) {
        ++batchIndex;
        uploadMessagesBatch(batch, batchIndex, uploadCallback);
    }
}

} // namespace maps::wiki::editor_takeout
