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

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

#include <boost/algorithm/string/split.hpp>
#include <vector>

namespace maps::wiki::social {

namespace {

const std::string COMMENT_TABLE = sql::table::COMMENT + " c";
const std::string COMMENT_TABLE_ALIAS = "c.";

const std::vector<std::string> COMMENT_FIELDS {
    sql::col::ID,
    sql::col::TYPE,
    sql::col::OBJECT_ID,
    sql::col::COMMIT_ID,
    sql::col::FEEDBACK_TASK_ID,
    sql::col::CREATED_BY,
    sql::col::CREATED_AT,
    sql::col::DELETED_BY,
    sql::col::DELETED_AT,
    sql::col::DATA,
    sql::col::INTERNAL
};

} // namespace

void
checkUid(TUid uid)
{
    REQUIRE(uid, "invalid uid");
}

void
checkAoi(TId aoiId)
{
    REQUIRE(aoiId, "aoi not defined");
}

void
checkAoi(const TIds& aoiIds)
{
    for (auto aoiId : aoiIds) {
        checkAoi(aoiId);
    }
}

void
checkLimit(size_t limit)
{
    REQUIRE(limit, "limit must be non-zero");
}

void
checkExpires(const std::string& expires)
{
    REQUIRE(!expires.empty(), "invalid expires");
}

std::set<std::string>
parseStringSetFromSqlArray(const std::string& data)
{
    REQUIRE(
        (data.front() == '{') && (data.back() == '}'),
        "Wrong array representation received"
    );
    auto isComma = [](char c)
    {
        return (c == ',');
    };
    CategoryIdsSet result;
    std::string dataWithoutParanthesis = data.substr(1, data.size() - 2);
    if (!dataWithoutParanthesis.empty()) {
        //boost::split behaviour is strange when input string is empty
        boost::split(result, dataWithoutParanthesis, isComma);
    }
    return result;
}

std::string
dumpToSqlArray(const std::set<std::string>& strings)
{
    return '{' + common::join(strings, ',') + '}';
}

std::string dumpIdsToSqlArray(const TIds& ids)
{
    std::set<std::string> strings;
    for (const auto id : ids) {
        strings.emplace(std::to_string(id));
    }
    return dumpToSqlArray(strings);
}

std::string unnestArray(pqxx::transaction_base& txn, const TIds& ids)
{
    return "UNNEST(" + txn.quote(dumpIdsToSqlArray(ids)) + "::bigint[])";
}

std::string
joinCommentFields()
{
    auto joinCommentField = [](const std::string& column)
    {
        return COMMENT_TABLE_ALIAS + column + " AS " +
               COMMENT_FIELDS_PREFIX + column;
    };

    return common::join(
        COMMENT_FIELDS.begin(),
        COMMENT_FIELDS.end(),
        joinCommentField,
        ','
    );
}

std::string
joinCommentTable(const std::string& parentTableAlias)
{
    return " LEFT JOIN " + COMMENT_TABLE +
            " ON " + parentTableAlias + sql::col::TYPE +
                    " != '" + sql::value::EVENT_TYPE_EDIT + "'"
            " AND " + parentTableAlias + sql::col::EVENT_ID +
                    " = " + COMMENT_TABLE_ALIAS + sql::col::EVENT_ID;
}

std::string
multirowValuesInsertStatement(
    const std::vector<std::vector<std::string>>& values)
{
    return common::join(
        values,
        [&](const std::vector<std::string>& row) {
            return "(" + common::join(row, ',') + ")";
        },
        ','
    );
}

std::string toPgValue(bool b)
{
    return b ? sql::value::TRUE : sql::value::FALSE;
}

std::string sqlIntervalInSeconds(std::chrono::seconds interval)
{
    return "'" + std::to_string(interval.count()) + " seconds'::interval";
}

std::string userMidnightExpression(int tzOffsetInMinutes)
{
    auto userDate =
        "(NOW()::timestamp WITHOUT TIME ZONE"
        " + AGE(NOW() AT TIME ZONE 'UTC', NOW())"
        " - '" + std::to_string(tzOffsetInMinutes) + " minutes'::interval)"
        "::date::timestamp WITHOUT TIME ZONE";
    return
        "((" + userDate +
        " + '" + std::to_string(tzOffsetInMinutes) + " minutes'::interval)"
        " AT TIME ZONE 'UTC')";
}

} // namespace maps::wiki::social
