#include "ugc_backoffice_client.h"

#include <maps/libs/common/include/retry.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/doc/proto/converters/geolib/include/yandex/maps/geolib3/proto.h>

#include <maps/libs/geolib/include/algorithm.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/linear_ring.h>
#include <maps/libs/geolib/include/point.h>
#include <maps/libs/geolib/include/polygon.h>
#include <maps/streetview/backoffice/tools/mrc_targets/include/proto_converters.h>

#include <yandex/maps/proto/common2/geometry.pb.h>
#include <yandex/maps/proto/ugc_account/backoffice.pb.h>

namespace maps::wiki::tasks::mrc_pedestrian {

namespace pugc = yandex::maps::proto::ugc_account;
namespace pgeo = yandex::maps::proto::common2::geometry;

namespace {

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


std::string makeCreateAssignmentRequestBody(
    const std::string& name,
    const std::string& uid,
    const geolib3::Polygon2& mercatorPolygon,
    bool isIndoor,
    bool isPanoramic,
    const std::vector<streetview::MrcTarget>& panoramaTargets)
{
    pugc::backoffice::Task task;
    *task.add_uid() = uid;
    auto polygon = geolib3::convertMercatorToGeodetic(mercatorPolygon);
    *task.mutable_point() = geolib3::proto::encode(geolib3::findCentroid(polygon));

    auto& langToMetadata = *task.mutable_lang_to_metadata();

    pugc::assignment::AssignmentMetadata metadata;
    auto* pedestrian = metadata.mutable_pedestrian_assignment();

    pedestrian->set_title(name.c_str());

    if (isIndoor) {
        pedestrian->set_is_indoor(true);
    }
    if (isPanoramic) {
        pedestrian->set_is_panoramic(true);
        for (const auto & panoramaTarget : panoramaTargets) {
            *pedestrian->add_panorama_targets() = streetview::encode(panoramaTarget);
        }
    }

    auto* geoObject = pedestrian->mutable_geo_object();
    auto* geometry = geoObject->add_geometry();
    *geometry->mutable_polygon() = geolib3::proto::encode(polygon);

    // TODO: add instruction url
    // auto* link = pedestrian->mutable_instruction_url();
    // *link->mutable_href() = "http://yandex.ru";

    langToMetadata["ru_RU"] = metadata;

    return task.SerializeAsString();
}

using RequestProvider = std::function<http::Request()>;

http::Response performRequestWithRetries(
    RequestProvider requestProvider,
    const common::RetryPolicy& policy)
{
    http::Request::Attempt attempt = http::Request::Attempt::First;

    return common::retry(
        [&]() {
            auto request = requestProvider();
            auto response = request.perform(attempt);
            attempt = http::Request::Attempt::Subsequent;
            return response;
        },
        policy,
        [](const auto& maybeResponse) {
            if (!maybeResponse.valid() ||
                maybeResponse.get().status_experimental().isServerError()) {
                return false;
            }
            return true;
        }
    );
}

static const TString UGC_BACKOFFICE_TVM_ALIAS = "ugc-backoffice";

} // anonymous namespace

UgcBackofficeClient::UgcBackofficeClient(
    std::string ugcBackofficeHost,
    std::unique_ptr<ServiceTicketProvider> serviceTicketProvider)
        : ugcBackofficeHost_(std::move(ugcBackofficeHost))
        , serviceTicketProvider_(std::move(serviceTicketProvider))
{
    REQUIRE(serviceTicketProvider_, "Invalid service ticket provider");
    INFO() << "Created ugc backoffice client with base url "
           << ugcBackofficeHost_;
}

void
UgcBackofficeClient::createAssignment(
    const std::string& taskId,
    const std::string& name,
    const std::string& uid,
    const geolib3::Polygon2& mercatorPolygon,
    bool isIndoor,
    bool isPanoramic,
    const std::vector<streetview::MrcTarget>& panoramaTargets)
{
    INFO() << "Creating assignment for taskId " << taskId << ", uid = " << uid;

    http::URL url{ugcBackofficeHost_};
    url.setPath("/v1/assignments/create");
    url.addParam("task_id", taskId);

    auto response = performRequestWithRetries(
        [&, this]{
            http::Request request(httpClient_, http::PUT, url);
            request.addHeader(auth::SERVICE_TICKET_HEADER,
                serviceTicketProvider_->getTicketFor(UGC_BACKOFFICE_TVM_ALIAS));
            request.addHeader("Content-Type", "application/x-protobuf");
            request.setContent(
                makeCreateAssignmentRequestBody(name, uid, mercatorPolygon, isIndoor,
                                                isPanoramic, panoramaTargets));
            return request;
        },
        RETRY_POLICY);

    REQUIRE(response.status_experimental().isSuccess(),
            "Failed to create assignment by url: " << url
                << ", http response status: " << response.status());
}

void
UgcBackofficeClient::deleteAssignment(
    const std::string taskId,
    const std::string& uid)
{
    INFO() << "Delete assignment for taskId " << taskId << ", uid " << uid;

    http::URL url{ugcBackofficeHost_};
    url.setPath("/v1/assignments/delete");
    url.addParam("task_id", taskId);
    url.addParam("uid", uid);

    auto response = performRequestWithRetries(
        [&, this]{
            http::Request request(httpClient_, http::DELETE, url);
            request.addHeader(auth::SERVICE_TICKET_HEADER,
                serviceTicketProvider_->getTicketFor(UGC_BACKOFFICE_TVM_ALIAS));
            return request;
        },
        RETRY_POLICY);

    if (response.status_experimental() == http::Status::NotFound) {
        INFO() << "Assignment for taskId " << taskId << " did not exist";
        return;
    }

    REQUIRE(response.status_experimental().isSuccess(),
            "Failed to delete assignment by url: " << url
                << ", http response status: " << response.status());
}

std::optional<pugc::assignment::Status>
UgcBackofficeClient::getAssignmentStatus(
    const std::string& taskId,
    const std::string& uid)
{
    INFO() << "Get assignment status for taskId " << taskId;

    http::URL url{ugcBackofficeHost_};
    url.setPath("/v1/assignments/status");
    url.addParam("task_id", taskId);
    url.addParam("uid", uid);

    auto response = performRequestWithRetries(
        [&, this]{
            http::Request request(httpClient_, http::GET, url);
            request.addHeader(auth::SERVICE_TICKET_HEADER,
                serviceTicketProvider_->getTicketFor(UGC_BACKOFFICE_TVM_ALIAS));
            return request;
        },
        RETRY_POLICY);

    if (response.status_experimental() == http::Status::NotFound) {
        return std::nullopt;
    }

    REQUIRE(response.status_experimental().isSuccess(),
            "Failed to get assignment status by url: " << url
                << ", http response status: " << response.status());

    pugc::backoffice::AssignmentStatus status;
    Y_PROTOBUF_SUPPRESS_NODISCARD status.ParseFromString(TString{response.readBody()});
    return status.status();
}

} // namespace maps::wiki::tasks::mrc_pedestrian
