#include "mrc_client.h"
#include "image_utils.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 <maps/libs/auth/include/tvm.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/geolib3/proto.h>

using namespace std::literals::chrono_literals;

namespace maps::wiki::toloka {

namespace {
const std::string CREATE_OBJECT_HANDLER = "/v2/objects/create";
const auto HTTP_OK = static_cast<int>(maps::http::Status::OK);
const auto HTTP_REQUEST_ENTITY_TOO_LARGE = static_cast<int>(maps::http::Status::PayloadTooLarge);
const auto HTTP_OVERLOAD = static_cast<int>(maps::http::Status::TooManyRequests);
const auto OVERLOAD_PAUSE = 100ms;

std::string toMessage(const CreateObjectRequest& request)
{
    REQUIRE(!request.photos.empty(), "Empty photos");

    yandex::maps::proto::mrc::backoffice::CreateObjectRequest message;
    message.set_type(yandex::maps::proto::mrc::backoffice::ObjectType::BUILDING_ENTRANCE);
    message.set_dataset(yandex::maps::proto::mrc::backoffice::Dataset::TOLOKA_PEDESTRIANS);

    *(message.mutable_point()) = geolib3::proto::encode(request.objectCoordinate);
    if (!request.comment.empty()) {
        message.set_comment(TString{request.comment});
    }

    for (const auto& [_, requestPhoto] : request.photos) {
        auto* photo = message.add_photos();
        const auto& imageData = requestPhoto.image.data();
        photo->set_image(imageData.c_str(), imageData.size());
        photo->set_taken_at(chrono::convertToUnixTime(requestPhoto.takenAt));

        auto* shootingPoint = photo->mutable_shooting_point();
        auto* point3d = shootingPoint->mutable_point();
        *(point3d->mutable_point()) = geolib3::proto::encode(requestPhoto.workerCoordinate);
        shootingPoint->mutable_direction()->set_azimuth(requestPhoto.azimuth);
        shootingPoint->mutable_direction()->set_tilt(0);
    }
    DEBUG() << message.DebugString();
    return message.SerializeAsString();
}

std::string getLargestImageName(const CreateObjectRequest& request)
{
    std::string photoName;
    size_t size = 0;
    for (const auto& [name, photo] : request.photos) {
        const auto& data = photo.image.data();
        if (size < data.size()) {
            size = data.size();
            photoName = name;
        }
    }
    REQUIRE(size && !photoName.empty(), "Big photo not found");
    INFO() << "Found original largest photo: " << photoName << " size: " << size;
    return photoName;
}

void shrinkLargestImage(CreateObjectRequest& request)
{
    INFO() << "Find largest image to fit";
    const auto name = getLargestImageName(request);

    auto& image = request.photos.at(name).image;
    image.shrinkScale++;
    INFO() << "Shrink scale: " << image.shrinkScale;
    image.shrinked = shrinkImage(image.original, image.shrinkScale);
    INFO() << "Shrinked size: " << image.shrinked.size();
}

CreateObjectResponse parseResponseBody(
    const CreateObjectRequest& createObjectRequest,
    const std::string& responseBody)
{
    yandex::maps::proto::mrc::backoffice::CreateObjectResponse message;
    Y_PROTOBUF_SUPPRESS_NODISCARD message.ParseFromString(TString{responseBody});
    size_t size = message.photo_ids_size();
    const auto& photos = createObjectRequest.photos;
    REQUIRE(size == photos.size(),
            "Wrong photo_ids size: " << size << " expected: " << photos.size());

    int index = 0;
    CreateObjectResponse result;
    for (const auto& [attachmentId, _] : photos) {
        const auto& photoId = message.photo_ids(index);
        result.attachmentToPhotoIds.emplace(attachmentId, photoId.c_str());
        ++index;
    }
    return result;
}

} // namespace

MrcClient::MrcClient(const std::string& mrcApiUrl)
    : mrcApiUrl_(mrcApiUrl)
{
}

CreateObjectResponse
MrcClient::submit(CreateObjectRequest& createObjectRequest)
{
    try {
        auto message = toMessage(createObjectRequest);
        while (true) {
            http::Request request(httpClient_, http::POST, mrcApiUrl_ + CREATE_OBJECT_HANDLER);
            request.addHeader(auth::SERVICE_TICKET_HEADER, tvmTicketProvider_());
            request.setContent(message);
            auto response = request.perform();
            const auto responseBody = response.readBody();
            const int status = response.status();
            if (status == HTTP_OK) {
                return parseResponseBody(createObjectRequest, responseBody);
            } else if (status == HTTP_REQUEST_ENTITY_TOO_LARGE) {
                WARN() << "Create object request, status: " << status;
                shrinkLargestImage(createObjectRequest);
                message = toMessage(createObjectRequest);
            } else if (status == HTTP_OVERLOAD) {
                std::this_thread::sleep_for(OVERLOAD_PAUSE);
            } else {
                ERROR() << "Failed to create object request, response: " << responseBody;
                return {};
            }
        }
    } catch (const std::exception& ex) {
        ERROR() << "Failed to create object request, error: " << ex.what();
    }
    return {};
}

} // namespace maps::wiki::toloka
