#include "mrc_client.h"

// https://a.yandex-team.ru/arc/trunk/arcadia/maps/doc/proto/yandex/maps/proto/mrc/backoffice/backoffice.proto
#include <yandex/maps/proto/mrc/backoffice/backoffice.pb.h>
#include <yandex/maps/geolib3/proto.h>

#include <maps/libs/auth/include/tvm.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/direction.h>

using namespace std::literals::chrono_literals;

namespace maps::wiki::walkers_export_downloader {

namespace {

const std::string CREATE_PHOTO_HANDLER = "/v2/photos/create";
const std::string CREATE_OBJECT_HANDLER = "/v2/objects/create";

const auto HTTP_OK = 200;
const auto HTTP_OVERLOAD = 429;
const auto HTTP_REQUEST_ENTITY_TOO_LARGE = 413;

const auto OVERLOAD_PAUSE = 100ms;
const auto STATUS_5XX_PAUSE = 10000ms;
const size_t QUERY_RETRIES = 5;

std::string
performRequest(
    const std::string& message,
    const TvmTicketProvider& tvmTicketProvider,
    http::Client& httpClient,
    const std::string& url)
{
    REQUIRE(tvmTicketProvider, "Tvm ticket is required for mrc access");
    try {
        size_t retries = 0;
        while (retries < QUERY_RETRIES) {
            ++retries;
            http::Request request(httpClient, http::POST, url);
            request.addHeader(auth::SERVICE_TICKET_HEADER, tvmTicketProvider());
            request.setContent(message);
            auto response = request.perform();
            auto responseBody = response.readBody();
            const auto status = response.status();
            if (status == HTTP_OK) {
                return responseBody;
            } else if (status == HTTP_REQUEST_ENTITY_TOO_LARGE) {
                WARN() << "Failed to perform mrc request status: " << HTTP_REQUEST_ENTITY_TOO_LARGE;
                return {};
            } else if (retries < QUERY_RETRIES) {
                if (status == HTTP_OVERLOAD) {
                    std::this_thread::sleep_for(OVERLOAD_PAUSE);
                } else {
                    std::this_thread::sleep_for(STATUS_5XX_PAUSE);
                }
            } else {
                throw maps::LogicError() <<
                    "Failed to perform mrc request, retries: "
                        << retries <<
                        " last status: " << status <<
                        " last response: " << responseBody;
            }
        }
    } catch (const std::exception& ex) {
        ERROR() << "MRC client error: " << ex.what();
        throw;
    }
    return {};
}

} // namespace

std::string
toMessage(const CreatePhotoRequest& createPhotoRequest)
{
    yandex::maps::proto::mrc::backoffice::CreatePhotoRequest message;
    message.set_dataset(yandex::maps::proto::mrc::backoffice::ALTAY_PEDESTRIANS);
    auto* photo = message.mutable_photo();
    photo->set_image(createPhotoRequest.image.c_str(), createPhotoRequest.image.size());
    photo->set_taken_at(chrono::convertToUnixTime(createPhotoRequest.takenAt));
    auto* point3d = photo->mutable_shooting_point()->mutable_point();
    *(point3d->mutable_point()) =  geolib3::proto::encode(geolib3::mercator2GeoPoint(createPhotoRequest.coordinate));
    photo->mutable_shooting_point()->mutable_direction()->set_azimuth(createPhotoRequest.azimuth);
    photo->mutable_shooting_point()->mutable_direction()->set_tilt(0);
    ASSERT(message.IsInitialized());
    return message.SerializeAsString();
}

std::string
toMessage(const CreateObjectRequest& createObjectRequest)
{
    const auto geodeticCoord = geolib3::mercator2GeoPoint(createObjectRequest.coordinate);
    const auto geodeticTarget = geolib3::mercator2GeoPoint(createObjectRequest.target);
    double azimuth = geolib3::geoDirection(
        {
            geodeticCoord,
            geodeticTarget
        }).heading().value();

    yandex::maps::proto::mrc::backoffice::CreateObjectRequest message;
    message.set_type(yandex::maps::proto::mrc::backoffice::ObjectType::UNKNOWN_OBJECT_TYPE);
    *(message.mutable_point()) = geolib3::proto::encode(geodeticTarget);
    message.set_dataset(yandex::maps::proto::mrc::backoffice::ALTAY_PEDESTRIANS);

    auto* photo = message.add_photos();
    photo->set_image(createObjectRequest.image.c_str(), createObjectRequest.image.size());
    photo->set_taken_at(chrono::convertToUnixTime(createObjectRequest.takenAt));
    auto* point3d = photo->mutable_shooting_point()->mutable_point();
    *(point3d->mutable_point()) =  geolib3::proto::encode(geodeticCoord);
    photo->mutable_shooting_point()->mutable_direction()->set_azimuth(azimuth);
    photo->mutable_shooting_point()->mutable_direction()->set_tilt(0);

    ASSERT(message.IsInitialized());
    return message.SerializeAsString();
}

MrcClient::MrcClient(std::string mrcApiUrl)
    : mrcApiUrl_(std::move(mrcApiUrl))
{
}

PhotoId
MrcClient::submitPhoto(const CreatePhotoRequest& createPhotoRequest)
{
    auto response = performRequest(
        toMessage(createPhotoRequest),
        tvmTicketProvider_,
        httpClient_,
        mrcApiUrl_ + CREATE_PHOTO_HANDLER);
    if (response.empty()) {
        return {};
    }
    yandex::maps::proto::mrc::backoffice::CreatePhotoResponse createPhotoResponse;
    REQUIRE(createPhotoResponse.ParseFromString(response.c_str()),
        "Failed to parse response: " << response);
    PhotoId photoId(createPhotoResponse.photo_id());
    REQUIRE(!photoId.empty(), "Empty photo id received: " << response);
    return photoId;
}

PhotoId
MrcClient::submitObject(const CreateObjectRequest& createObjectRequest)
{
    auto response = performRequest(
        toMessage(createObjectRequest),
        tvmTicketProvider_,
        httpClient_,
        mrcApiUrl_ + CREATE_OBJECT_HANDLER);
    if (response.empty()) {
        return {};
    }
    yandex::maps::proto::mrc::backoffice::CreateObjectResponse сreateObjectResponse;
    REQUIRE(сreateObjectResponse.ParseFromString(response.c_str()),
        "Failed to parse response: " << response);
    REQUIRE(сreateObjectResponse.photo_ids_size() == 1,
        "Unexpected number of photoIds returned: " << сreateObjectResponse.photo_ids_size());
    PhotoId photoId(сreateObjectResponse.photo_ids(0));
    REQUIRE(!photoId.empty(), "Empty photo id received: " << response);
    return photoId;
}

} // namespace maps::wiki::walkers_export_downloader
