#include "tasks_gateway.h"

#include <maps/libs/http/include/http.h>
#include <maps/libs/xml/include/xml.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/common/include/exception.h>

#include <chrono>
#include <thread>
#include <iterator>

namespace {

using namespace maps::wiki::tasks_gateway;

const std::string ID_ATTR("id");
const std::chrono::seconds RETRY_DELAY(10);

TaskId extractId(const std::string& text)
{
    auto doc = maps::xml3::Doc::fromString(text);
    doc.addNamespace("t", "http://maps.yandex.ru/mapspro/tasks/1.x");
    auto node = doc.node("/t:tasks/t:task", /* doNotThrow = */ true);
    REQUIRE(!node.isNull(), "Can't find task node in response: " << text);
    return node.attr<TaskId>(ID_ATTR);
}
} // anonymous namespace

namespace maps {
namespace wiki {
namespace tasks_gateway {

TaskId createTask(const std::string& serviceUrl,
                  const std::string& taskType,
                  UserId userId,
                  const Params& extraParams)
{
    REQUIRE(!serviceUrl.empty(), "Service URL must not be empty");
    REQUIRE(!taskType.empty(), "Task type must not be empty");

    http::Client client;
    constexpr std::size_t maxAttempts = 3;
    std::size_t attempt = 0;

    std::string path = serviceUrl;
    if (path.back() != '/') {
        path += "/";
    }
    path += "tasks";

    while (attempt < maxAttempts) {
        ++attempt;
        try {
            http::Request request(client, http::POST, path);
            request.addParam("uid", std::to_string(userId));
            request.addParam("type", taskType);

            for (const auto& param : extraParams) {
                request.addParam(param.first, param.second);
            }

            auto response = request.perform();
            auto status = response.status();
            auto body = response.readBody();
            if (200 <= status && status < 300) {
                return extractId(body);
            } else if (status >= 500) {
                WARN() << "Attempt #" << attempt
                       << ": failed to create task of type " << taskType
                       << " because of server error: status = " << status
                       << ", body: " << body;
            } else if (status >= 400) {
                throw maps::Exception() << "Failed to create task of type "
                                        << taskType << " at URL " << path
                                        << ": " << body;
            }
        }
        catch (const http::Error& e) {
            WARN() << "Attempt #" << attempt
                   << " - failed to create task of type " << taskType
                   << " at URL " << path << ": " << e.what();
        }
        std::this_thread::sleep_for(RETRY_DELAY);
    }
    throw maps::Exception() << "Failed to create task of type " << taskType
                            << " after " << maxAttempts << " attempts";
}

} // namespace tasks_gateway
} // namespace wiki
} // namespace maps
