#include "create.h"

#include <maps/wikimap/mapspro/libs/social/factory.h>
#include <maps/wikimap/mapspro/libs/social/magic_strings.h>
#include <maps/wikimap/mapspro/libs/social/suspicious_users.h>

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

#include <sstream>

namespace maps::wiki::social::tasks {

namespace {

bool
selectIsUserNovice(
    pqxx::transaction_base& txn,
    TUid uid,
    const std::string& userCreatedOrUnbannedAt)
{
    const auto afterBanInterval = "'48 hours'::interval";
    const auto firstCommitAge = "'24 hours'::interval";
    const auto acceptedTasksAmount = "15";
    const auto uidClause = sql::col::UID + " = " + std::to_string(uid);

    const auto query =
        "SELECT (" // recently unbanned users are novices
        "    SELECT " + txn.quote(userCreatedOrUnbannedAt) + " >= NOW() - " + afterBanInterval +
        ") OR COALESCE (\n"
        "    (\n" // users with statistics but without info about the first
                  // commit are old (their first commits were made prio to this
                  // mechanism introduction)
        "        SELECT CASE WHEN " + sql::col::FIRST_COMMIT_AT + " IS NULL THEN FALSE\n"
        "                    ELSE " + sql::col::FIRST_COMMIT_AT + " >= NOW() - " + firstCommitAge + "\n"
        "               END\n"
        "        FROM " + sql::table::STATS + "\n"
        "        WHERE " + uidClause + "\n"
        "    ), TRUE\n" // users without statistics are novices
        ") OR COALESCE (\n"
        "    (\n" // Check skills
        "        SELECT SUM(" + sql::col::AMOUNT + ") <= " + acceptedTasksAmount + "\n"
        "        FROM " + sql::table::SKILLS + "\n"
        "        WHERE\n"
        "            " + uidClause + " AND\n"
        "            " + sql::col::RESOLVE_RESOLUTION + " = " + sql::value::RESOLVE_RESOLUTION_ACCEPT + "\n"
        "        GROUP BY " + sql::col::UID + "\n"
        "    ), TRUE\n" // users without calculated skills are novices.
        ")";

    return txn.exec(query)[0][0].as<bool>();
}

pqxx::result
insertIntoTaskActive(
    pqxx::transaction_base& work,
    const Event& event,
    Accepted accepted,
    bool isUserNovice)
{
    std::vector<std::string> columns {
        sql::col::EVENT_ID,
        sql::col::TYPE,
        sql::col::CREATED_AT
    };

    std::vector<std::string> values {
        std::to_string(event.id()),
        work.quote(boost::lexical_cast<std::string>(event.type())),
        sql::value::NOW
    };

    if (event.commitData()) {
        columns.emplace_back(sql::col::COMMIT_ID);
        values.emplace_back(std::to_string(event.commitData()->commitId()));
    }

    if (event.primaryObjectData()) {
        columns.emplace_back(sql::col::PRIMARY_OBJECT_CATEGORY_ID);
        values.emplace_back(work.quote(event.primaryObjectData()->categoryId()));
    }

    if (accepted == Accepted::Yes) {
        columns.emplace_back(sql::col::RESOLVED_BY);
        values.emplace_back(std::to_string(common::ROBOT_UID));

        columns.emplace_back(sql::col::RESOLVED_AT);
        values.emplace_back(sql::value::NOW);

        columns.emplace_back(sql::col::RESOLVE_RESOLUTION);
        values.emplace_back(
            work.quote(
                boost::lexical_cast<std::string>(ResolveResolution::Accept)
            )
        );
    }

    if (isUserNovice) {
        columns.emplace_back(sql::col::IS_CREATED_BY_NOVICE);
        values.emplace_back(sql::value::TRUE);
    }

    const auto result = work.exec(
        "INSERT INTO " + sql::table::TASK_ACTIVE + " "
        "(" + common::join(columns, ',') + ") VALUES "
        "(" + common::join(values, ',') + ") RETURNING *"
    );

    ASSERT(result.size() == 1);
    return result;
}

} // namespace

Task
create(
    pqxx::transaction_base& work,
    Event event,
    const std::string& userCreatedOrUnbannedAt,
    Accepted accepted)
{
    const auto isUserNovice = selectIsUserNovice(work, event.createdBy(), userCreatedOrUnbannedAt);
    const auto taskRows = insertIntoTaskActive(work, event, accepted, isUserNovice);
    suspicious_users::onTaskCreated(work, event.createdBy(), userCreatedOrUnbannedAt);
    return Factory::component<Task>(taskRows[0], std::move(event));
}

} // namespace maps::wiki::social::tasks
