#include "../include/pedestrian_region.h"

#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/serialization.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <sstream>

namespace maps::wiki::pedestrian {

namespace {

const std::string TABLE = "service.pedestrian_region";

namespace col {
const std::string OBJECT_REGION_ID = "object_region_id";
const std::string POINTS = "points";
const std::string MODIFIED_BY = "modified_by";
const std::string MODIFIED_AT = "modified_at";
} // namespace col

static const std::string FIELDS_TO_SELECT = common::join(
    std::vector<std::string> {
        col::OBJECT_REGION_ID,
        col::POINTS,
        col::MODIFIED_BY
    }, ",");

static const std::string FIELDS_TO_INSERT = common::join(
    std::vector<std::string> {
        col::OBJECT_REGION_ID, col::POINTS, col::MODIFIED_BY, col::MODIFIED_AT
    }, ",");

Region regionByRow(const pqxx::row& row)
{
    return Region(
        row[col::OBJECT_REGION_ID].as<uint64_t>(),
        json::Value::fromString(row[col::POINTS].as<std::string>()),
        row[col::MODIFIED_BY].as<uint64_t>());
}

std::vector<geolib3::Point2> pointsToVector(const json::Value& jsonValue)
{
    std::vector<geolib3::Point2> retVal;
    ASSERT("GeometryCollection" == jsonValue["type"].as<std::string>());
    const json::Value& geometries = jsonValue["geometries"];
    for (const auto& geometry : geometries) {
        auto geoPoint = geolib3::readGeojson<geolib3::Point2>(geometry);
        retVal.emplace_back(geolib3::convertGeodeticToMercator(geoPoint));
    }
    return retVal;
}

std::string pointsToJsonString(const std::vector<geolib3::Point2>& points)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder builder) {
        builder["type"] = "GeometryCollection";
        builder["geometries"] = [&](json::ArrayBuilder builder) {
            for (const auto& point : points) {
                builder << geolib3::geojson(geolib3::convertMercatorToGeodetic(point));
            }
        };
    };
    return builder.str();
}

} // unnamed namespace

Region::Region(uint64_t objectRegionId, const json::Value& pointsJson, uint64_t modifiedBy)
    : objectRegionId_(objectRegionId)
    , mercPoints_(pointsToVector(pointsJson))
    , modifiedBy_(modifiedBy)
{
}

std::optional<Region> regionById(pqxx::transaction_base& coreTxn, uint64_t objectRegionId)
{
    std::stringstream query;

    query << "SELECT " << FIELDS_TO_SELECT
        << " FROM " << TABLE
        << " WHERE " << col::OBJECT_REGION_ID << "=" << objectRegionId;

    auto rows = coreTxn.exec(query.str());
    if (rows.empty()) {
        return std::nullopt;
    }
    return regionByRow(rows[0]);
}

void upsertRegion(pqxx::transaction_base& coreTxn, const Region& region)
{
    std::stringstream query;

    auto pointsJsonString = coreTxn.quote(pointsToJsonString(region.mercPoints()));

    query << "INSERT INTO " << TABLE
        << "(" << FIELDS_TO_INSERT << ")"
        << "VALUES ("
            << region.objectRegionId()
            << "," << pointsJsonString
            << "," << region.modifiedBy()
            << ", NOW()"
        << ")"
        << "ON CONFLICT (" << col::OBJECT_REGION_ID << ") DO UPDATE SET"
            << " " << col::POINTS << "=" << pointsJsonString
            << "," << col::MODIFIED_BY << "=" << region.modifiedBy()
            << "," << col::MODIFIED_AT << "= NOW()";

    coreTxn.exec(query.str());
}

} // namespace maps::wiki::pedestrian
