#include <maps/wikimap/mapspro/services/tasks-ng/lib/tasks.h>

#include <maps/wikimap/mapspro/services/tasks-ng/lib/exception.h>
#include <maps/wikimap/mapspro/services/tasks-ng/lib/magic_strings.h>
#include <maps/wikimap/mapspro/services/tasks-ng/lib/query.h>
#include <maps/wikimap/mapspro/services/tasks-ng/lib/task.h>
#include <maps/wikimap/mapspro/services/tasks-ng/lib/task_module_registry.h>
#include <yandex/maps/wiki/common/paged_result.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <vector>

namespace maps {
namespace wiki {
namespace tasks_ng {

namespace {

void makeResponseModifiedTask(
    DbContext& ctx,
    XmlResponseWrapper& wrapper,
    const Task& task)
{
    auto token = ctx.commit(TokenPolicy::Generate);

    auto& writer = wrapper.writer();
    wrapper.write([&] {
        task.write(wrapper.writer(), TaskWriteMode::Brief);
        writer.addTag("internal", [&] {
            writer.addTagData("token", token);
        });
    });
}

Task getTask(DbContext& ctx, Id taskId)
{
    auto query = Query()
        .select(BaseTask::fields())
        .from(sql::table::TASK)
        .where() << "id=" << taskId;

    auto res = ctx.txnCore().exec(query.str());
    TASKS_REQUIRE(
        !res.empty(), ERR_NOT_FOUND, "task id: " << taskId << " not found");

    Task task(res[0]);
    task.load(ctx);
    return task;
}

} // namespace

std::string getTaskLog(DbContext& ctx, Id taskId)
{
    auto query = Query()
        .select("log")
        .from(sql::table::TASK)
        .where() << "id=" << taskId;
    auto res = ctx.txnCore().exec(query.str());

    return res.empty() ? std::string() : res[0][0].as<std::string>({});
}

void makeResponseSetTaskStatus(
    DbContext& ctx,
    XmlResponseWrapper& wrapper,
    Id taskId,
    Uid uid,
    ServiceTaskStatus status)
{
    TASKS_REQUIRE(
        uid, ERR_FORBIDDEN, "Unknown user on update task: " << taskId);

    if (status == ServiceTaskStatus::Revoked) {
        auto task = getTask(ctx, taskId);
        task.revoke(ctx, uid);
        makeResponseModifiedTask(ctx, wrapper, task);
    } else if (status == ServiceTaskStatus::Pending) {
        auto task = getTask(ctx, taskId);
        task.resume(ctx, uid);
        makeResponseModifiedTask(ctx, wrapper, task);
    } else {
        THROW_TASKS_ERROR(ERR_BAD_REQUEST, "Can't set status to '" << status << "'");
    }
}

void makeResponseTask(
    DbContext& ctx,
    XmlResponseWrapper& wrapper,
    Id taskId)
{
    auto task = getTask(ctx, taskId);

    wrapper.write([&] {
        task.write(wrapper.writer(), TaskWriteMode::Full);
    });
}

void makeResponseTasks(
    DbContext& ctx,
    XmlResponseWrapper& wrapper,
    const std::string& taskType,
    Uid createdBy,
    Id parentId,
    size_t page,
    size_t perPage)
{
    auto& txnCore = ctx.txnCore();

    std::list<std::string> conds;
    if (createdBy) {
        conds.emplace_back("created_by=" + std::to_string(createdBy));
    }
    if (parentId) {
        conds.emplace_back("parent_id=" + std::to_string(createdBy));
    }
    if (!taskType.empty()) {
        conds.emplace_back("type=" + txnCore.quote(taskType));
    }

    auto commonQuery = Query().from(sql::table::TASK);
    if (!conds.empty()) {
        commonQuery.where(wiki::common::join(conds, " AND "));
    }

    auto countQuery = Query().count() << commonQuery;
    auto total = txnCore.exec(countQuery.str())[0][0].as<size_t>();

    wiki::common::Pager pager(total, page, perPage);

    auto selectQuery = Query()
        .select(BaseTask::fields())
        .append(commonQuery)
        .orderBy("id DESC")
        .offset(pager.offset())
        .limit(pager.limit());
    auto rows = txnCore.exec(selectQuery.str());

    std::vector<Task> tasks;
    tasks.reserve(rows.size());
    for (const auto& row : rows) {
        tasks.emplace_back(row);
    }

    for (auto& task : tasks) {
        task.load(ctx);
    }

    auto& writer = wrapper.writer();

    wrapper.write([&] {
        writer.addTag("task-list", [&] {
            writer.addPager(pager);
            for (const auto& task : tasks) {
                task.write(writer, TaskWriteMode::Brief);
            }
        });
    });
}

void makeResponseCapabilities(
    XmlResponseWrapper& wrapper,
    const std::string& taskType)
{
    auto& writer = wrapper.writer();

    auto writeModule = [&writer](const TaskModule& module) {
        writer.addTag("task-type", [&] {
            writer.addAttribute("id", module.name());
            module.capabilities(writer);
        });
    };

    wrapper.write([&] {
        writer.addTag("capabilities", [&] {
            if (taskType.empty()) {
                for (const auto& module : TaskModuleRegistry::get().allModules()) {
                    writeModule(*module);
                }
            } else {
                writeModule(TaskModuleRegistry::get().module(taskType));
            }
        });
    });
}

void makeResponseCreateTask(
    DbContext& ctx,
    XmlResponseWrapper& wrapper,
    const std::string& taskType,
    Uid uid,
    Id parentId,
    const RequestParameters& parameters)
{
    Task task(ctx, taskType, uid, parentId, parameters);
    ctx.commit(TokenPolicy::Skip);

    task.launch(ctx, uid);
    makeResponseModifiedTask(ctx, wrapper, task);
}

} // namespace tasks_ng
} // namespace wiki
} // namespace maps
