#include "find_nearby.h"

#include <maps/libs/geolib/include/conversion.h>

namespace maps::wiki::socialsrv {

namespace mws = social;
namespace mwsf = social::feedback;

namespace {

const auto DOUBLE_FORMAT_PRECISION = 12;

const std::string TABLE_TRUNK_OBJECTS_P = "vrevisions_trunk.objects_p_view";

const std::set<std::string> ENTRANCE_SOURCES {
    "partner-pedestrian-onfoot",
    "sprav-pedestrian-onfoot", // yang
    "toloka-pedestrian-onfoot",
};

constexpr double FIND_ENTRANCE_DISTANCE = 10; // in meters
const std::string CAT_ENTRANCE = "cat:poi_entrance";
const std::string ENTRANCE_CLAUSE =
    "domain_attrs -> 'poi_entrance:type' IS NULL"; // building entrance (not business, service)

std::string makeQueryPosition(const geolib3::Point2& position)
{
    std::ostringstream query;
    query.precision(DOUBLE_FORMAT_PRECISION);
    query << "ST_SetSRID(ST_Point("
          << position.x() << ", " << position.y() << "), 3395)";
    return query.str();
}

mws::TId findNearestPointObject(
    IDbPoolsWithViewTrunk& dbPools,
    const geolib3::Point2& position,
    const std::string& category,
    const std::string& clause,
    double distance)
{
    auto positionStr = makeQueryPosition(position);
    auto distanceMercator = geolib3::toMercatorUnits(distance, position);

    auto txn = dbPools.viewTrunkTxn();

    auto makeWhereClause = [&] {
        std::ostringstream query;
        query.precision(DOUBLE_FORMAT_PRECISION);
        query <<
            "ST_DWithin(the_geom, " << positionStr << ", " << distanceMercator << ")"
            " AND domain_attrs ? " + txn->quote(category);
        if (!clause.empty()) {
            query << " AND " << clause;
        }
        return query.str();
    };

    auto query =
        "SELECT id"
        " FROM " + TABLE_TRUNK_OBJECTS_P +
        " WHERE " + makeWhereClause() +
        " ORDER BY ST_Distance(the_geom, " + positionStr + ")"
        " LIMIT 1";

    auto rows = txn->exec(query);
    if (rows.empty()) {
        return 0;
    }
    return rows.front().front().as<mws::TId>();
}

} // namespace

void findNearbyObject(
    IDbPoolsWithViewTrunk& dbPools,
    serialize::TaskForUI& task)
{
    if (task.objectId()) {
        return;
    }

    if (task.type() == mwsf::Type::Entrance &&
        ENTRANCE_SOURCES.contains(task.source()))
    {
        auto objectId = findNearestPointObject(
            dbPools, task.position(),
            CAT_ENTRANCE, ENTRANCE_CLAUSE, FIND_ENTRANCE_DISTANCE);
        if (objectId) {
            task.setNearbyObjectId(objectId);
        }
    }
}

} // namespace maps::wiki::socialsrv
