#include "ymapsdf_loader.h"

#include <yandex/maps/wiki/common/string_utils.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/serialization.h>
#include <maps/libs/geolib/include/polyline.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/deprecated/localeutils/include/locale.h>

#include <pqxx/binarystring>
#include <pqxx/result>

#include <string>
#include <vector>

namespace maps::mrc::gen_targets {

MultiDistrict loadAdDistrictFromRow(const pqxx::row& row)
{
    std::string name = row["name"].as<std::string>();
    std::string lang = row["lang"].as<std::string>();
    std::string extlang = row["extlang"].as<std::string>("");
    std::string script = row["script"].as<std::string>("");
    std::string region = row["region"].as<std::string>("");
    std::string variant  = row["variant"].as<std::string>("");

    Language language(langCodeByName(lang),
                      langCodeByName(extlang),
                      scriptCodeByName(script),
                      regionCodeByName(region));
    Locale locale(language,
                  regionCodeByName(region),
                  variantCodeByName(variant));

    auto geomField = row["shape"];
    REQUIRE(!geomField.is_null(), "AD object " + name + " has empty geom field");
    std::string geomWkb = pqxx::binarystring(geomField).str();
    geolib3::MultiPolygon2 geom;
    // db can contain Polygon2 or MultiPolygon2
    try {
        std::stringstream geomWkbStream(geomWkb);
        geom = geolib3::WKB::read<geolib3::MultiPolygon2>(geomWkbStream);
    } catch (const maps::Exception&) {
        std::stringstream geomWkbStream(geomWkb);
        geolib3::Polygon2 polygon = geolib3::WKB::read<geolib3::Polygon2>(geomWkbStream);
        geom = geolib3::MultiPolygon2(std::vector<geolib3::Polygon2>{polygon});
    }

    return MultiDistrict{geom, DistrictName{name, locale}};
}

MultiDistrict YmapsdfDataLoader::loadAdDistrict(AdId adId)
{
    std::stringstream q;
    q << "SELECT ST_AsBinary(ad_geom.shape) shape, "
      << "       ad_nm.name, ad_nm.lang, ad_nm.extlang, "
      << "       ad_nm.script, ad_nm.region, ad_nm.variant "
      << "FROM ad_geom, ad_nm "
      << "WHERE ad_geom.ad_id = " << adId << " AND ad_nm.ad_id = ad_geom.ad_id "
      << "      AND ad_nm.is_local AND NOT ad_nm.is_auto "
      << "      AND ad_nm.name_type <= 1 "
      << "ORDER BY ad_nm.name_type DESC " // name_type = 1 is better
      << "LIMIT 1 ";

    // see https://doc.yandex-team.ru/ymaps/ymapsdf/ymapsdf-ref/concepts/ad.html

    INFO() << "Executing load district query: " << q.str();

    auto result = txn_.exec(q.str());
    REQUIRE(result.size(), "Can't load district: query has returned 0 rows");
    const auto& row = result.begin();
    return loadAdDistrictFromRow(row);
}

std::vector<MultiDistrict> YmapsdfDataLoader::loadAdSubdistricts(AdId parentAdId,
                                                                 int levelKind,
                                                                 int depth)
{
    std::stringstream q;
    q << "WITH RECURSIVE r AS ( "
      << "    SELECT *, 0 as depth "
      << "    FROM ad "
      << "    WHERE ad_id = " << parentAdId

      << "    UNION "

      << "    SELECT ad.*, r.depth + 1 AS depth "
      << "    FROM ad "
      << "    JOIN r "
      << "    ON ad.p_ad_id = r.ad_id AND ad.level_kind <= " << levelKind << " "
      << ") "

      << "SELECT ad_id "
      << "FROM r ";
    if (depth == NO_SUBDISTRICTS_DEPTH) {
        q << "WHERE (level_kind = " << levelKind
          << "       OR ad_id = " << parentAdId << " ) "
          << "      AND ad_id NOT IN (select p_ad_id from r) ";
    } else {
        q << "WHERE level_kind = " << levelKind
          << "      AND depth = " << depth;
    }

    INFO() << "Executing load districts query: " << q.str();

    std::vector<MultiDistrict> result;
    for (const auto& row : txn_.exec(q.str())) {
        Id adId = row["ad_id"].as<Id>();
        result.push_back(loadAdDistrict(adId));
    }
    return result;
}

} // namespace maps::mrc::gen_targets
