#include <yandex/maps/wiki/social/comment.h>
#include "comment_impl.h"

#include "helpers.h"
#include "magic_strings.h"

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

#include <sstream>

namespace maps::wiki::social {

Comment::Comment(const pqxx::row& row, const std::string& columnPrefix)
    : id_(row[columnPrefix + sql::col::ID].as<TId>())
    , type_(enum_io::fromString<CommentType>(row[columnPrefix + sql::col::TYPE].c_str()))
    , objectId_(row[columnPrefix + sql::col::OBJECT_ID].as<TId>())
    , feedbackTaskId_(
        row[columnPrefix + sql::col::FEEDBACK_TASK_ID].is_null()
        ? std::optional<TId>(std::nullopt)
        : std::optional<TId>(row[columnPrefix + sql::col::FEEDBACK_TASK_ID].as<TId>()))
    , commitId_(row[columnPrefix + sql::col::COMMIT_ID].as<TId>())
    , createdBy_(row[columnPrefix + sql::col::CREATED_BY].as<TUid>())
    , createdAt_(row[columnPrefix + sql::col::CREATED_AT].c_str())
    , deletedBy_(row[columnPrefix + sql::col::DELETED_BY].as<TUid>(0))
    , deletedAt_(row[columnPrefix + sql::col::DELETED_AT].c_str())
    , internal_(
        row[columnPrefix + sql::col::INTERNAL].as<bool>()
        ? Comment::Internal::Yes
        : Comment::Internal::No)
    , data_(row[columnPrefix + sql::col::DATA].c_str())
{}

Comment
Comment::create(
    pqxx::transaction_base& txn,
    TUid uid, CommentType type, const std::string& data,
    TId commitId, TId objectId, std::optional<TId> feedbackTaskId, TId eventId,
    Comment::Internal internal)
{
    checkUid(uid);

    std::vector<std::string> fieldNames{
        sql::col::TYPE,
        sql::col::OBJECT_ID,
        sql::col::COMMIT_ID,
        sql::col::CREATED_BY,
        sql::col::DATA
    };

    std::vector<std::string> fieldValues{
        txn.quote(std::string{toString(type)}),
        std::to_string(objectId),
        std::to_string(commitId),
        std::to_string(uid),
        "btrim(" + txn.quote(data) + ", ' \n')"
    };

    if (feedbackTaskId) {
        fieldNames.push_back(sql::col::FEEDBACK_TASK_ID);
        fieldValues.push_back(std::to_string(*feedbackTaskId));
    }
    if (eventId) {
        fieldNames.push_back(sql::col::EVENT_ID);
        fieldValues.push_back(std::to_string(eventId));
    }
    if (internal == Comment::Internal::Yes) {
        fieldNames.push_back(sql::col::INTERNAL);
        fieldValues.push_back(sql::value::TRUE);
    }

    std::ostringstream query;
    query << "INSERT INTO " << sql::table::COMMENT << " "
        << "(" << common::join(fieldNames, ',') << ")"
        << " VALUES (" << common::join(fieldValues, ',') + ")"
        << " RETURNING *";

    auto r = txn.exec(query.str());
    ASSERT(r.size() == 1);

    return Comment(r[0]);
}

std::optional<Comment>
Comment::loadFromJoined(const pqxx::row& row)
{
    if (row[COMMENT_FIELDS_PREFIX + sql::col::ID].as<TId>(0)) {
        return Comment(row, COMMENT_FIELDS_PREFIX);
    }
    return std::nullopt;
}

bool
Comment::deleteBy(pqxx::transaction_base& work, TUid uid)
{
    checkUid(uid);

    std::ostringstream query;
    query <<
        "UPDATE " << sql::table::COMMENT <<
        " SET " << sql::col::DELETED_BY << "=" << uid << ","
                << sql::col::DELETED_AT << "=" + sql::value::NOW <<
        " WHERE " <<
            sql::col::ID << "=" << id() << " AND " <<
            sql::col::DELETED_BY << "=0"
        " RETURNING *";
    auto r = work.exec(query.str());
    if (r.empty()) {
        return false;
    }

    *this = Comment(r[0]);

    return true;
}

void
bindCommentForAoi(pqxx::transaction_base& txn, TId commentId, const TIds& aoiIds)
{
    if (aoiIds.empty()) {
        return;
    }

    checkAoi(aoiIds);

    std::vector<std::string> columns {
        sql::col::AOI_ID,
        sql::col::COMMENT_ID
    };

    auto valuesMatrix = [&](){
        std::vector<std::vector<std::string>> result;
        std::for_each(
            aoiIds.begin(), aoiIds.end(),
            [&](TId aoiId){
                result.push_back({
                    std::to_string(aoiId),
                    std::to_string(commentId)
                });
            }
        );
        return result;
    }();

    txn.exec(
        "INSERT INTO " + sql::table::AOI_COMMENT + " (" +
        common::join(columns, ',') + ") VALUES " +
        multirowValuesInsertStatement(valuesMatrix)
    );
}

} // namespace maps::wiki::social
