#include <maps/wikimap/mapspro/libs/taskutils/include/taskmanager.h>

#include <maps/wikimap/mapspro/libs/taskutils/impl/reader.h>
#include <maps/wikimap/mapspro/libs/taskutils/impl/tasks_impl.h>
#include <maps/wikimap/mapspro/libs/taskutils/impl/taskmanager_impl.h>
#include <maps/wikimap/mapspro/libs/taskutils/include/exception.h>

#include <boost/format.hpp>

namespace maps::wiki::taskutils {

namespace {

const std::string DB_SCHEMA_DEFAULT = "service";

const boost::format FORMAT_CREATE(
    "INSERT INTO %1%.taskutils_tasks"
    " (service_name, task_name, expires, input_data, metadata_json, created_by)"
    " VALUES (%2%, %3%, NOW() + interval '%4% seconds', %5%, %6%, %7%)"
    " RETURNING id");

const boost::format FORMAT_CANCEL(
    "UPDATE %1%.taskutils_tasks SET status='canceled', finished=NOW()"
    " WHERE status IN ('created','started') AND id=%2%");

const boost::format FORMAT_TERMINATE(
    "UPDATE %1%.taskutils_tasks SET status='terminated', finished=NOW()"
    " WHERE status IN ('created','started') AND id IN (%2%)");

const boost::format FORMAT_REMOVE_EXPIRED(
    "DELETE FROM %1%.taskutils_tasks"
    " WHERE expires < (NOW() - interval '%2% seconds')");

const boost::format FORMAT_FIND_TASKS(
    "SELECT id, created_by, extract(epoch from expires)::bigint FROM %1%.taskutils_tasks "
    " WHERE NOW() < expires"
    " AND metadata_json @> %2% and status IN %4% "
    " AND created_by = %3%;");
const std::string STR_ACTIVE_STATUSES = "('created', 'started')";
const std::string STR_ACTIVE_AND_DONE_STATUSES = "('created', 'started', 'done')";

} // namespace


TaskManager::TaskManager(const std::string& tokenSecretWord, const std::string& dbSchema)
    : impl_(new TaskManagerImpl(tokenSecretWord, dbSchema.empty() ? DB_SCHEMA_DEFAULT : dbSchema))
{
}

TaskManager::~TaskManager()
{
}

const std::string&
TaskManager::tokenSecretWord() const
{
    return impl_->tokenSecretWord_;
}

TaskResult
TaskManager::load(Transaction& work, const Token& token) const
{
    Reader reader(impl_->commonData_->dbSchema());
    return reader.load(work, token);
}

TaskResult
TaskManager::load(Transaction& work, TaskID id) const
{
    Reader reader(impl_->commonData_->dbSchema());
    return reader.load(work, id);
}

Task
TaskManager::create(
    Transaction& work, const TaskInfo& taskInfo, TUid uid, size_t deadlineTimeout) const
{
    TransactionAutoCleaner<Transaction> guard(work);

    if (!deadlineTimeout) {
        throw WrongEmptyTimeoutException() << "empty deadline timeout on create task";
    }

    time_t expires = time(0) + deadlineTimeout;

    const auto& schema = impl_->commonData_->dbSchema();
    auto query = (boost::format(FORMAT_CREATE) %
        schema %
        work.quote(taskInfo.serviceName()) %
        work.quote(taskInfo.taskName()) %
        deadlineTimeout %
        work.quote(taskInfo.inputData()) %
        work.quote(taskInfo.metadataJson()) %
        uid).str();

    auto r = work.exec(query);
    if (r.empty()) {
        throw InvalidResponseException() << "task creation failed";
    }

    auto id = r[0][0].as<TaskID>();
    Token token(id, uid, expires, impl_->tokenSecretWord_);
    token.checkExpires();

    guard.commit();

    return impl_->createTask(taskInfo, token);
}

bool
TaskManager::cancel(Transaction& work, TaskID id) const
{
    TransactionAutoCleaner<Transaction> guard(work);

    const auto& schema = impl_->commonData_->dbSchema();
    auto query = (boost::format(FORMAT_CANCEL) % schema % id).str();

    auto r = work.exec(query);
    if (!r.affected_rows()) {
        return false;
    }

    guard.commit();
    return true;
}

void
TaskManager::terminateAll(Transaction& work) const
{
    TransactionAutoCleaner<Transaction> guard(work);

    std::set<TaskID> ids;
    impl_->commonData_->swapTasks(ids);
    if (ids.empty()) {
        return;
    }

    std::ostringstream ss;
    auto it = ids.begin();
    ss << *it++;
    while (it != ids.end()) {
        ss << ',' << *it++;
    }

    const auto& schema = impl_->commonData_->dbSchema();
    auto query = (boost::format(FORMAT_TERMINATE) % schema % ss.str()).str();
    work.exec(query);

    guard.commit();
}

size_t
TaskManager::removeExpired(Transaction& work, size_t keepTime) const
{
    TransactionAutoCleaner<Transaction> guard(work);

    const auto& schema = impl_->commonData_->dbSchema();
    auto query = (boost::format(FORMAT_REMOVE_EXPIRED) %
        schema % keepTime).str();
    auto rows = work.exec(query).affected_rows();

    guard.commit();
    return rows;
}

Tokens
TaskManager::findTasksTokens(Transaction& work,
    const std::string& metadataPatternJson, TUid createdBy, TaskManager::FindPolicy findPolicy) const
{
    const auto& schema = impl_->commonData_->dbSchema();
    auto query = (boost::format(FORMAT_FIND_TASKS) %
        schema %
        work.quote(metadataPatternJson) %
        createdBy %
        (findPolicy == TaskManager::FindPolicy::Active
            ? STR_ACTIVE_STATUSES
            : STR_ACTIVE_AND_DONE_STATUSES)).str();
    auto rows = work.exec(query);
    Tokens tokens;
    tokens.reserve(rows.size());
    for (const auto& row : rows) {
        tokens.emplace_back(
            row[0].as<TaskID>(),
            row[1].as<TUid>(),
            row[2].as<time_t>(),
            impl_->tokenSecretWord_);
    }
    return tokens;
}

} // namespace maps::wiki::taskutils
