#include "sprav.h"

#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/geolib3/proto.h>
#include <yandex/maps/proto/search/experimental.pb.h>
#include <yandex/maps/proto/search/references.pb.h>
#include <yandex/maps/wiki/common/string_utils.h>
#include <util/string/cast.h>

namespace maps::wiki::socialsrv {

namespace mp = yandex::maps::proto;
using social::TId;

namespace {

const auto NYAK = "nyak";
const auto CLUSTER_PERMALINKS = "cluster_permalinks";

template <typename T>
std::optional<TId> toId(const T& value)
{
    TId id;
    if(!TryFromString(value, id)) {
        return std::nullopt;
    }
    return id;
}

template <typename Visitor>
void forEachMetadata(
    const mp::common2::response::Response& response,
    const Visitor& visit)
{
    for (const auto& geoObject : response.reply().geo_object()) {
        for (const auto& metadata : geoObject.metadata()) {
            visit(metadata);
        }
    }
}

std::set<TId> extractNyakIds(const mp::common2::response::Response& response)
{
    std::set<TId> result;
    auto loadIds = [&] (const auto& references) {
        for (const auto& reference : references) {
            if (reference.scope() != NYAK) {
                return;
            }
            if (auto id = toId(reference.id())) {
                result.emplace(*id);
            }
        }
    };

    forEachMetadata(response, [&] (const auto& metadata) {
        if (!metadata.HasExtension(mp::search::references::GEO_OBJECT_METADATA)) {
            return;
        }
        const auto extension = metadata.GetExtension(mp::search::references::GEO_OBJECT_METADATA);
        loadIds(extension.reference());
    });
    return result;
}

std::set<TId> extractPermalinkIds(const mp::common2::response::Response& response)
{
    std::set<TId> result;
    auto parsePermalinks = [&] (const auto& permalinks) {
        for (const auto& value : common::split(permalinks, ",")) {
            if (auto id = toId(value)) {
                result.emplace(*id);
            }
        }
    };

    auto loadIds = [&] (const auto& items) {
        for (const auto& item : items) {
            if (item.key() == CLUSTER_PERMALINKS) {
                parsePermalinks(item.value());
            }
        }
    };

    forEachMetadata(response, [&] (const auto& metadata) {
        if (!metadata.HasExtension(mp::search::experimental::GEO_OBJECT_METADATA)) {
            return;
        }
        const auto extension = metadata.GetExtension(mp::search::experimental::GEO_OBJECT_METADATA);
        if (extension.has_experimental_storage()) {
            loadIds(extension.experimental_storage().item());
        }
    });
    return result;
}

std::optional<maps::geolib3::Point2> extractGeometry(const mp::common2::response::Response& response)
{
    for (const auto& geoObject : response.reply().geo_object()) {
        for (const auto& geometry : geoObject.geometry()) {
            return maps::geolib3::proto::decode(geometry.point());
        }
    }
    return std::nullopt;
}

} // namespace

std::optional<SpravData> loadSpravData(
    geosearch_client::Client& geosearchClient,
    TId businessId)
{
    auto response = geosearchClient.search(geosearch_client::SearchParams()
        .addType(geosearch_client::Type::Biz)
        .addBusinessOid(std::to_string(businessId))
        .addSnippet(geosearch_client::Snippet::ClusterPermalinks)
    );
    DEBUG() << response.DebugString();

    auto geometry = extractGeometry(response);
    if (!geometry) {
        return std::nullopt;
    }

    SpravData result{
        .businessId = businessId,
        .positionGeo = *geometry,
        .clusterPermalinkIds = extractPermalinkIds(response),
        .objectIds = extractNyakIds(response)
    };
    result.clusterPermalinkIds.emplace(businessId);

    return std::move(result);
}

} // namespace maps::wiki::socialsrv
