#include <yandex/maps/wiki/http/blackbox/blackbox.h>

#include "http_utils.h"

#include <maps/libs/auth/include/tvm.h>
#include <maps/libs/http/include/url.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/locale/include/convert.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/common/include/exception.h>

namespace maps::wiki::blackbox {

Configuration::Configuration(std::string host)
    : host_(std::move(host))
{}

const std::string&
Configuration::host() const
{
    return host_;
}

Configuration&
Configuration::setHost(std::string host)
{
    host_ = std::move(host);
    return *this;
}


UserInfo::UserInfo()
    : uid_(0)
{}

const std::string&
UserInfo::email() const
{
    return email_;
}

UserInfo&
UserInfo::setEmail(std::string email)
{
    email_ = std::move(email);
    return *this;
}

const std::optional<locale::Locale>&
UserInfo::locale() const
{
    return locale_;
}

UserInfo&
UserInfo::setLocale(std::optional<locale::Locale> locale)
{
    locale_ = std::move(locale);
    return *this;
}

const std::optional<std::string>&
UserInfo::username() const
{
    return username_;
}

UserInfo&
UserInfo::setUsername(std::optional<std::string> username)
{
    username_ = std::move(username);
    return *this;
}


UID
UserInfo::uid() const
{
    return uid_;
}

UserInfo&
UserInfo::setUid(UID uid)
{
    uid_ = uid;
    return *this;
}

namespace {

const std::string USER_IP_BLACKBOX = "127.0.0.1";
const std::string PERSON_LANG_ATTR_NUM = "34";
const std::chrono::milliseconds HTTP_TIMEOUT = std::chrono::seconds{3};

json::Value
performGetRequest(
    const http::URL& url,
    const maps::common::RetryPolicy& retryPolicy,
    const TvmTicketProvider& tvmTicketProvider)
{
    http::Client httpClient;
    httpClient.setTimeout(HTTP_TIMEOUT);

    auto singlePerform = [&]() {
        http::Request request(httpClient, http::GET, url);
        if (tvmTicketProvider) {
            request.addHeader(auth::SERVICE_TICKET_HEADER, tvmTicketProvider());
        }
        return request.perform();
    };

    auto response = performHttpRequest(singlePerform, retryPolicy);

    REQUIRE(
        response.status() == http_status::OK,
        "unexpected http code: " << response.status() << "; GET url: " << url
    );

    auto responseJson = json::Value(response.body());

    // Error can exist in body if for example you don't have grants
    // for some attributes of user info. But the response status code is 200
    //
    REQUIRE(
        !responseJson.hasField("error"),
        "error: " << responseJson["error"].as<std::string>() << "; GET url: " << url
    );

    return responseJson;
}

std::optional<json::Value>
extractUserValue(const json::Value& responseValue)
{
    if (!responseValue.hasField("users")) {
        return std::nullopt;
    }
    auto usersValue = responseValue["users"];
    if (!usersValue.isArray() || usersValue.empty()) {
        return std::nullopt;
    }
    return usersValue[0];
}

std::optional<std::string>
extractDefaultAddress(const json::Value& userValue)
{
    if (!userValue.hasField("address-list")) {
        return std::nullopt;
    }

    auto addressList = userValue["address-list"];
    auto defaultAddress = std::find_if(
        addressList.begin(), addressList.end(),
        [](const json::Value& addressValue) {
            return addressValue.hasField("default") &&
                   addressValue["default"].as<bool>();
        }
    );

    if (defaultAddress != addressList.end()) {
        return (*defaultAddress)["address"].as<std::string>();
    }
    return std::nullopt;
}

std::optional<std::string>
extractUserName(const json::Value& userValue)
{
    if (userValue.hasField("display_name")) {
        auto displayNameValue = userValue["display_name"];
        if (displayNameValue.hasField("name")) {
            auto name = displayNameValue["name"].as<std::string>();
            if (!name.empty()) {
                return name;
            }
        }
    }
    return std::nullopt;
}

std::optional<locale::Locale>
extractLocale(const json::Value& userValue)
{
    if (userValue.hasField("attributes")) {
        auto attributes = userValue["attributes"];
        if (attributes.hasField(PERSON_LANG_ATTR_NUM)) {
            auto lang = attributes[PERSON_LANG_ATTR_NUM].as<std::string>();
            if (!lang.empty()) {
                return locale::to<locale::Locale>(lang);
            }
        }
    }
    return std::nullopt;
}

} // unnamed namespace

Gateway::Gateway(
    Configuration configuration,
    const maps::common::RetryPolicy& retryPolicy,
    TvmTicketProvider tvmTicketProvider) :
        configuration_(std::move(configuration)),
        retryPolicy_(retryPolicy),
        tvmTicketProvider_ (tvmTicketProvider)
{}

bool
Gateway::isEmailValid(const std::string& email, UID uid) const
{
    http::URL url("http://" + configuration_.host() + "/blackbox");
    url.addParam("method", "userinfo");
    url.addParam("uid", std::to_string(uid));
    url.addParam("userip", USER_IP_BLACKBOX);
    url.addParam("addrtotest", email);
    url.addParam("emails", "testone");
    url.addParam("format", "json");

    const auto& responseValue = performGetRequest(url, retryPolicy_, tvmTicketProvider_);

    auto userValue = extractUserValue(responseValue);

    return userValue && userValue->hasField("address-list") && !(*userValue)["address-list"].empty();
}

std::optional<UserInfo>
Gateway::defaultUserInfo(UID uid) const
{
    http::URL url("http://" + configuration_.host() + "/blackbox");
    url.addParam("method", "userinfo");
    url.addParam("uid", std::to_string(uid));
    url.addParam("userip", USER_IP_BLACKBOX);
    url.addParam("attributes", PERSON_LANG_ATTR_NUM);
    url.addParam("regname", "yes");
    url.addParam("emails", "getdefault");
    url.addParam("format", "json");

    const auto& responseValue = performGetRequest(url, retryPolicy_, tvmTicketProvider_);
    auto userValue = extractUserValue(responseValue);

    if (!userValue) {
        return std::nullopt;
    }

    auto defaultAddress = extractDefaultAddress(*userValue);
    if (!defaultAddress) {
        return std::nullopt;
    }

    UserInfo userInfo;
    userInfo.setEmail(defaultAddress.value());
    userInfo.setLocale(extractLocale(*userValue));
    userInfo.setUsername(extractUserName(*userValue));
    userInfo.setUid(uid);
    return userInfo;
}

} // namespace maps::wiki::blackbox
