#pragma once

#include <yandex/maps/wiki/tasks/task_info.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/common/include/retry.h>

#include <boost/optional.hpp>

#include <chrono>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>

namespace maps {
namespace wiki {
namespace tasks {

const boost::none_t NO_AOI = boost::none;
const boost::none_t NO_REGION = boost::none;
const boost::none_t HEAD_COMMIT = boost::none;
const boost::none_t NO_PARENT = boost::none;

/**
Http response status is not 200
*/
struct ResponseError : public maps::Exception
{
};

enum class ExportTested
{
    Yes,
    No
};

enum class ContentType
{
    Form,
    Xml
};

/**
Sends http requests to tasks servant to start task
*/
class TaskManager
{
public:
    TaskManager(std::string tasksUrl, UserId uid);

    TaskInfo startCreateStableBranch(BranchId approvedBranchId) const;

    TaskInfo startApplyShadowAttributes(BranchId stableBranchId) const;

    TaskInfo startValidation(
        BranchId branchId,
        PresetId presetId,
        boost::optional<ObjectId> aoiId = NO_AOI,
        boost::optional<ObjectId> regionId = NO_REGION,
        boost::optional<CommitId> commitId = HEAD_COMMIT) const;

    TaskInfo startValidation(
        BranchId branchId,
        const std::set<std::string>& checks,
        boost::optional<ObjectId> aoiId = NO_AOI,
        boost::optional<ObjectId> regionId = NO_REGION,
        boost::optional<CommitId> commitId = HEAD_COMMIT,
        boost::optional<TaskId> parentId = NO_PARENT) const;

    TaskInfo startDiffalert(
        BranchId oldBranchId,
        BranchId newBranchId) const;

    TaskInfo startExport(
        BranchId stableBranchId,
        const std::string& subset,
        ExportTested tested = ExportTested::No,
        boost::optional<CommitId> commitId = HEAD_COMMIT) const;

    TaskInfo taskInfo(
        TaskId taskId,
        const Token& token = Token(),
        maps::common::RetryPolicy retryPolicy = maps::common::RetryPolicy()
            .setTryNumber(5)
            .setInitialCooldown(std::chrono::seconds(1))) const;

    TaskInfo resumeTask(TaskId taskId) const;

    void changeTaskParameters(TaskId taskId, const std::string& body) const;

    using CheckCancelCallback = std::function<void()>;

    struct TaskWaitResult
    {
        std::unordered_map<TaskId, TaskStatus> taskStatuses;
        std::unordered_set<TaskId> unavailableTasks; //due to http errors

        bool isSuccess(TaskId taskId) const;
    };

    /**
    Polls statuses of tasks while they have status TaskStatus::InProgress
    Returns statuses of all tasks
    */
    TaskWaitResult waitForTasks(
        TaskInfos taskInfos,
        std::chrono::seconds duration,
        CheckCancelCallback checkCancel) const;

private:
    boost::optional<TaskStatus> getTaskStatus(const TaskInfo& task) const;

    std::string tasksUrl_;
    UserId uid_;
};

} // namespace tasks
} // namespace wiki
} // namespace maps
