#include "staff.h"

#include <maps/wikimap/mapspro/libs/http/http_utils.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <maps/libs/http/include/http.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/common/include/retry.h>
#include <maps/libs/common/include/make_batches.h>

#include <string>

using namespace std::chrono_literals;

namespace maps::wiki {

namespace {

const auto RETRY_POLICY = maps::common::RetryPolicy()
    .setTryNumber(5)
    .setInitialCooldown(1s)
    .setCooldownBackoff(2.0);

const std::string ACCOUNT_TYPE_NMAPS = "nmaps_login";
const std::string FIELDS_PARAM = "login,accounts.type,accounts.value,official.is_dismissed";
const size_t MAX_LOGINS_PER_REQUEST = 30;
} // namespace

Staff::Staff(const std::string& urlBase, const std::string& oAuthToken)
    : urlBase_(urlBase)
{
    headers_["Authorization"] = "OAuth " + oAuthToken;
}

StaffUsers
Staff::getUsersByDepartment(const std::string& departmentUrl) const
{
    auto url = makeUsersByDepartmentUrl(departmentUrl);

    auto [responseBody, status] = client_.get(url, headers_, RETRY_POLICY);
    auto responseJson = json::Value::fromString(responseBody);

    ASSERT(responseJson.hasField("pages"));
    auto numPages = responseJson["pages"].as<int>();

    StaffUsers result;
    parseJsonResponseUsers(responseJson, result);

    for (int page = 2; page <= numPages; ++page) {
        auto url = makeUsersByDepartmentUrl(departmentUrl, page);
        auto [responseBody, status] = client_.get(url, headers_, RETRY_POLICY);
        auto responseJson = json::Value::fromString(responseBody);
        parseJsonResponseUsers(responseJson, result);
    }

    return result;
}

StaffUsers
Staff::getUsersByLogins(const std::set<std::string>& logins) const
{
    if (logins.empty()) {
        return {};
    }
    StaffUsers result;
    const auto batches = maps::common::makeBatches(logins, MAX_LOGINS_PER_REQUEST);
    for (const auto batch : batches) {
        auto [responseBody, status] = client_.get(makeUsersByLoginsUrl(
            {batch.begin(), batch.end()}),
            headers_,
            RETRY_POLICY);
        auto responseJson = json::Value::fromString(responseBody);
        parseJsonResponseUsers(json::Value::fromString(responseBody), result);
    }
    return result;
}

http::URL
Staff::makeUsersByDepartmentUrl(
    const std::string& departmentUrl,
    int page) const
{
    http::URL url = urlBase_ + "persons";
    url.addParam("_query",
        "department_group.department.url==\"" + departmentUrl + "\"" +
        " or " +
        "department_group.ancestors.department.url==\"" + departmentUrl + "\"");
    url.addParam("_page", page);
    url.addParam("_fields", FIELDS_PARAM);
    return url;
}

http::URL
Staff::makeUsersByLoginsUrl(const std::set<std::string>& logins) const
{
    http::URL url = urlBase_ + "persons";
    url.addParam("login", common::join(logins, ","));
    url.addParam("_fields", FIELDS_PARAM);
    return url;
}

void
Staff::parseJsonResponseUsers(
    const json::Value& responseJson,
    StaffUsers& users) const
{
    ASSERT(responseJson.hasField("result"));
    for (const auto& personJson : responseJson["result"]) {
        REQUIRE(personJson.hasField("accounts") && personJson.hasField("login"),
            "Unexpected response format");
        StaffUser user;
        user.login = personJson["login"].as<std::string>();
        for (const auto& accountJson : personJson["accounts"]) {
            ASSERT(accountJson.hasField("type"));
            if (!accountJson["type"].isString() ||
                accountJson["type"].as<std::string>() != ACCOUNT_TYPE_NMAPS)
            {
                continue;
            }
            ASSERT(accountJson.hasField("value"));
            user.nmapsLogins.push_back(accountJson["value"].as<std::string>());
        }

        ASSERT(
            personJson.hasField("official") &&
            personJson["official"].hasField("is_dismissed"));
        user.dismissed = static_cast<Dismissed>(
            personJson["official"]["is_dismissed"].as<bool>());

        users.push_back(user);
    }
}

} // maps::wiki
