#include "client_impl.h"
#include "magic_strings.h"
#include "task_suite_impl.h"
#include <yandex/maps/mrc/toloka_client/filter.h>
#include <yandex/maps/mrc/toloka_client/task_suite.h>
#include <maps/libs/json/include/exception.h>

namespace maps {
namespace mrc {
namespace toloka {
namespace io {

namespace {

constexpr size_t DEFAULT_PAGE_LIMIT = 50;

} // anonymous namespace

json::Value
TolokaClient::Impl::requestTaskSuitesPage(const Filter& filter,
                                          size_t limit) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/task-suites";

    auto response = retry([&](){
        http::Request request(*httpClient, http::GET, url);
        addAuthHeader(request);
        request.addParam(PARAM_SORT, FIELD_ID);
        request.addParam(PARAM_LIMIT, std::to_string(limit));
        filter.apply<TaskSuite>(request);

        return performRequestChecked(request, url);
    });

    try {
        return json::Value::fromStream(response.body());
    } catch (const maps::json::ParserError& e) {
        throw ServerError() << "Failed to parse server response, url: " << url
                << ", " << e.what();
    }
}



TaskSuitesResponse TolokaClient::Impl::getTaskSuites(Filter filter) const
{
    bool hasMore = true;
    std::string lastId;
    TaskSuites taskSuites;

    while (hasMore) {
        if (!lastId.empty()) {
            filter.idGreaterThan(lastId);
        }

        auto jsonBody = requestTaskSuitesPage(filter, DEFAULT_PAGE_LIMIT);
        hasMore = jsonBody[FIELD_HAS_MORE].as<bool>();

        for (const auto& item : jsonBody[FIELD_ITEMS]) {
            taskSuites.push_back(PImplFactory::create<TaskSuite>(item));
        }
        if (!taskSuites.empty()) {
            lastId = taskSuites.back().id();
        }
    }
    return PImplFactory::create<TaskSuitesResponse>(std::move(taskSuites), false);
}

TaskSuitesResponse TolokaClient::Impl::getTaskSuites(const Filter& filter,
                                                     size_t limit) const
{
    TaskSuites taskSuites;
    auto jsonBody = requestTaskSuitesPage(filter, limit);
    bool hasMore = jsonBody[FIELD_HAS_MORE].as<bool>();
    for (const auto& item : jsonBody[FIELD_ITEMS]) {
        taskSuites.push_back(PImplFactory::create<TaskSuite>(item));
    }
    return PImplFactory::create<TaskSuitesResponse>(std::move(taskSuites), hasMore);
}

TaskSuite TolokaClient::Impl::getTaskSuite(const std::string& taskSuiteId) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/task-suites/" + taskSuiteId;

    auto response = retry([&](){
        http::Request request(*httpClient, http::GET, url);
        addAuthHeader(request);

        return performRequestChecked(request, url);
    });

    try {
        auto jsonBody = json::Value::fromStream(response.body());
        return PImplFactory::create<TaskSuite>(jsonBody);
    } catch (const maps::json::ParserError& e) {
        throw ServerError() << "Failed to parse server response, url: " << url
                << ", " << e.what();
    }
}

TaskSuite TolokaClient::Impl::postTaskSuite(
    const TaskSuiteCreationParams& taskSuiteCreationParams,
    const StringMap& params) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/task-suites";

    auto response = retry([&](){
        http::Request request(*httpClient, http::POST, url);
        addAuthHeader(request);
        addContentTypeHeader(request);
        for (const auto& param : params) {
            request.addParam(param.first, param.second);
        }
        std::stringstream ss;
        ss << taskSuiteCreationParams;
        request.setContent(ss);

        return performRequestChecked(request, url);
    });

    try {
        auto jsonBody = json::Value::fromStream(response.body());
        return PImplFactory::create<TaskSuite>(jsonBody);
    } catch (const maps::json::ParserError& e) {
        throw ServerError() << "Failed to parse server response, url: " << url
                << ", " << e.what();
    }
}

TaskSuitesPostResponse TolokaClient::Impl::postTaskSuites(
    const std::vector<TaskSuiteCreationParams>& taskSuiteCreationParamsVec,
    const StringMap& params) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/task-suites";

    auto response = retry([&](){
        http::Request request(*httpClient, http::POST, url);
        addAuthHeader(request);
        addContentTypeHeader(request);
        for (const auto& param : params) {
            request.addParam(param.first, param.second);
        }
        std::stringstream ss;
        ss << taskSuiteCreationParamsVec;
        request.setContent(ss);

        return performRequestChecked(request, url);
    });

    try {
        auto jsonBody = json::Value::fromStream(response.body());
        std::map<size_t, TaskSuite> taskSuites;
        std::map<size_t, json::Value> errors;

        const auto& items = jsonBody[FIELD_ITEMS];
        const auto& validationErrors = jsonBody[FIELD_VALIDATION_ERRORS];

        for (const auto& key : items.fields()) {
            taskSuites.emplace(boost::lexical_cast<size_t>(key),
                               PImplFactory::create<TaskSuite>(items[key]));
        }
        for (const auto& key : validationErrors.fields()) {
            errors.emplace(boost::lexical_cast<size_t>(key),
                           validationErrors[key]);
        }

        return PImplFactory::create<TaskSuitesPostResponse>(
                std::move(taskSuites), std::move(errors));
    } catch (const maps::json::ParserError& e) {
        throw ServerError() << "Failed to parse server response, url: " << url
                << ", " << e.what();
    }
}

TaskSuite TolokaClient::Impl::patchTaskSuite(const std::string& taskSuiteId,
                                             const std::string& data) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/task-suites/" + taskSuiteId;

    auto response = retry([&](){
        http::Request request(*httpClient, http::PATCH, url);
        addAuthHeader(request);
        addContentTypeHeader(request);
        request.setContent(data);
        return performRequestChecked(request, url);
    });

    try {
        auto jsonBody = json::Value::fromStream(response.body());
        return PImplFactory::create<TaskSuite>(jsonBody);
    } catch (const maps::json::ParserError& e) {
        throw ServerError() << "Failed to parse server response, url: " << url
                << ", " << e.what();
    }
}

TaskSuite TolokaClient::Impl::stopAssigningTaskSuite(const std::string& taskSuiteId) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/task-suites/" + taskSuiteId + "/set-overlap-or-min";

    json::Builder builder;
    builder << [=](json::ObjectBuilder b) {
        b[FIELD_OVERLAP] = 0;
        b[FIELD_INFINITE_OVERLAP] = false;
    };

    auto response = retry([&](){
        http::Request request(*httpClient, http::PATCH, url);
        addAuthHeader(request);
        addContentTypeHeader(request);
        request.setContent(builder.str());
        return performRequestChecked(request, url);
    });

    try {
        auto jsonBody = json::Value::fromStream(response.body());
        return PImplFactory::create<TaskSuite>(jsonBody);
    } catch (const maps::json::ParserError& e) {
        throw ServerError() << "Failed to parse server response, url: " << url
                << ", " << e.what();
    }
}

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

