#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/opencv.h>
#include <yandex/maps/mrc/traffic_signs/signs.h>

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/toloka/task_gateway.h>
#include <maps/libs/pgpool/include/pgpool3.h>

#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/conversion.h>

#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/approvement.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/image_quality_classification.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/taxonomy.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/traffic_light_detection.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/house_number_detection.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/house_number_recognition.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/detection_pair_match.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/detection_missing_on_frame.h>

namespace maps {
namespace mrc {
namespace toloka {

template <typename TaskType>
db::toloka::Tasks makeEmptyDbTasks(DbPlatform platform, size_t count, int overlap)
{
    auto createdAt = chrono::TimePoint::clock::now();
    db::toloka::Tasks dbTasks;
    dbTasks.reserve(count);

    for (size_t i = 0; i < count; ++i) {
        db::toloka::Task dbTask(platform);
        dbTask.setType(TaskType::DB_TASK_TYPE)
            .setStatus(TaskStatus::New)
            .setInputValues(std::string()) // will be reset later
            .setOverlap(overlap)
            .setCreatedAt(createdAt);
        dbTasks.push_back(std::move(dbTask));
    }
    return dbTasks;
}


TaskIds getTaskIds(pqxx::transaction_base& txn, DbPlatform platform, DbTaskType dbTaskType);

TaskStatus getTaskStatus(pqxx::transaction_base& txn, TaskId taskId);

std::map<TaskId, TaskStatus>
getTaskStatuses(pqxx::transaction_base& txn, const TaskIds& taskIds);

void freeTask(pqxx::transaction_base& txn, TaskId taskId);
void freeTasks(pqxx::transaction_base& txn, const TaskIds& taskIds);

void cancelTask(pqxx::transaction_base& txn, TaskId taskId);
void cancelTasks(pqxx::transaction_base& txn, const TaskIds& taskIds);

traffic_signs::TrafficSign trafficSignFromString(const std::string& name);

template <typename TaskType>
std::vector<TaskType> createTasks(
    pqxx::transaction_base& txn,
    DbPlatform platform,
    const std::vector<InputType<TaskType>>& inputs,
    const std::vector<KnownSolutions<TaskType>>& inputsOfKnownSolutions = {},
    const std::vector<std::string>& inputsMessageOnUnkownSolution = {})
{
    ASSERT(inputsOfKnownSolutions.size() == 0
           || inputsOfKnownSolutions.size() == inputs.size());
    ASSERT(inputsMessageOnUnkownSolution.size() == 0
           || inputsMessageOnUnkownSolution.size() == inputs.size());
    if (inputs.empty()) {
        return std::vector<TaskType>{};
    }

    std::vector<TaskType> result;
    result.reserve(inputs.size());

    auto dbTasks = makeEmptyDbTasks<TaskType>(platform,
                                              inputs.size(),
                                              TaskType::DEFAULT_OVERLAP);
    // Acquire task IDs
    db::toloka::TaskGateway{txn}.insertx(dbTasks);

    for (size_t i = 0; i < inputs.size(); ++i) {
        auto& dbTask = dbTasks[i];

        dbTask.setInputValues(toJson(inputs[i]));
        if (!inputsOfKnownSolutions.empty()) {
            auto knownSolutions = inputsOfKnownSolutions[i];
            dbTask.setKnownSolutions(toJson(knownSolutions));
        }
        if (!inputsMessageOnUnkownSolution.empty()) {
            dbTask.setMessageOnUnknownSolution(
                inputsMessageOnUnkownSolution[i]);
        }

        result.push_back(TaskType(dbTask.id(), TaskStatus::New,
                                  dbTask.createdAt(),
                                  inputs[i]));
    }
    db::toloka::TaskGateway{txn}.updatex(dbTasks);

    return result;
}

template <typename TaskType>
TaskType createTask(pqxx::transaction_base& txn,
                    DbPlatform platform,
                    const InputType<TaskType>& input,
                    const KnownSolutions<TaskType>& knownSolutions = {},
                    const std::string& messageOnUnknownSolution = {})
{
    auto tasks = createTasks<TaskType>(txn, platform, {input}, {knownSolutions},
                                       {messageOnUnknownSolution});
    return std::move(tasks[0]);
}

template <typename TaskType>
TaskType getTask(pqxx::transaction_base& txn, TaskId taskId)
{
    auto dbTask = db::toloka::TaskGateway{txn}.loadById(taskId);
    return TaskType(dbTask);
}

template <typename TaskType>
std::vector<TaskType> getTasks(pqxx::transaction_base& txn, const TaskIds& taskIds)
{
    auto dbTasks = db::toloka::TaskGateway{txn}.loadByIds(taskIds);

    std::vector<TaskType> result;
    result.reserve(taskIds.size());
    for (const auto& dbTask : dbTasks) {
        result.push_back(TaskType(dbTask));
    }
    return result;
}

template <typename TaskType>
std::vector<TaskType> getTasks(pqxx::transaction_base& txn, DbPlatform platform, TaskStatus status)
{
    auto taskIds = db::toloka::TaskGateway{txn}.loadIdsByTypeStatus(
        platform, TaskType::DB_TASK_TYPE, status);

    return getTasks<TaskType>(txn, taskIds);
}

} // namespace toloka
} // namespace mrc
} // namespace maps
