#include "client_impl.h"
#include "magic_strings.h"
#include "pool_impl.h"
#include "operation_impl.h"
#include <yandex/maps/mrc/toloka_client/filter.h>
#include <yandex/maps/mrc/toloka_client/pool.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::requestPoolsPage(const Filter& filter, size_t limit) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/pools";

    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<Pool>(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();
    }
}


PoolsResponse TolokaClient::Impl::getPools(Filter filter) const
{
    bool hasMore = true;
    std::string lastId;
    Pools pools;

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

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

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


PoolsResponse TolokaClient::Impl::getPools(const Filter& filter,
                                           size_t limit) const
{
    Pools pools;
    auto jsonBody = requestPoolsPage(filter, limit);
    bool hasMore = jsonBody[FIELD_HAS_MORE].as<bool>();
    for (const auto& item : jsonBody[FIELD_ITEMS]) {
        pools.push_back(PImplFactory::create<Pool>(item));
    }
    return PImplFactory::create<PoolsResponse>(std::move(pools), hasMore);
}

Pool TolokaClient::Impl::getPool(const std::string& poolId) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/pools/" + poolId;

    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<Pool>(jsonBody);
    } catch (const maps::json::ParserError& e) {
        throw ServerError() << "Failed to parse server response, url: " << url
                << ": " << e.what();
    }
}

Pool TolokaClient::Impl::createPool(
    const PoolCreationParams& poolCreationParams) const
{
    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/pools";

    auto response = retry([&](){
        http::Request request(*httpClient, http::POST, url);
        addAuthHeader(request);
        addContentTypeHeader(request);
        std::stringstream ss;
        ss << poolCreationParams;
        request.setContent(ss);
        return performRequestChecked(request, url);
    });

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

Operation TolokaClient::Impl::setPoolStatus(const std::string& poolId,
                                            PoolStatus desiredStatus) const
{
    std::string status;
    switch (desiredStatus) {
    case PoolStatus::Open:
        status = "/open";
        break;
    case PoolStatus::Closed:
        status = "/close";
        break;
    case PoolStatus::Archived:
        status = "/archive";
        break;
    default:
        throw Error() << "Bad pool status: "
                      << static_cast<int>(desiredStatus);
    }

    auto httpClient = makeHttpClient();
    http::URL url = schema_ + host_ + "/api/v1/pools/" + poolId + status;

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

        return performRequestChecked(request, url);
    });

    try {
        auto jsonBody = json::Value::fromStream(response.body());
        return PImplFactory::create<Operation>(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
