#include "sprav_poi_signal.h"

#include "maps/wikimap/mapspro/services/editor/src/objects_cache.h"
#include "maps/wikimap/mapspro/services/editor/src/objects/category_traits.h"
#include "maps/wikimap/mapspro/services/editor/src/srv_attrs/generic.h"

#include <yandex/maps/wiki/common/robot.h>

namespace maps::wiki {
using namespace poi_feed;
namespace {

const auto MERGE_POI_RECENT_PATCHES_TABLE = "sprav.merge_poi_recent_patches"s;
const auto OBJECT_ID_FIELD = "object_id"s;
const auto DATA_JSON_FIELD = "data_json"s;

FeedObjectData::Position
getPosition(const GeoObject* object)
{
    auto curCenterMercGeom = object->geom().center();
    auto curCenterMercCoord = curCenterMercGeom->getCoordinate();
    auto center = common::mercatorToGeodetic(
        curCenterMercCoord->x,
        curCenterMercCoord->y);
    return {center.x(), center.y()};
}

FtTypeId
getFtTypeId(const GeoObject* object)
{
    auto def = srv_attrs::ftTypeAttrDefForCategory(object->categoryId());
    ASSERT(def);
    return boost::lexical_cast<FtTypeId>(object->attributes().value(def->id()));
}

void
fillNames(const GeoObject* object, FeedObjectData& feedObjectData)
{
    const auto& nmAttrId = nameAttrId(object->categoryId());
    const auto& nameValues = object->tableAttributes().find(nmAttrId);
    if (nameValues.empty()) {
        return;
    }
    const auto& nameAttr = findAttrColumnNameBySuffix(nmAttrId, NM_NAME_SUFFIX);
    const auto& langAttr = findAttrColumnNameBySuffix(nmAttrId, NM_LANG_SUFFIX);
    const auto& typeAttr = findAttrColumnNameBySuffix(nmAttrId, NM_TYPE_SUFFIX);
    for (size_t r = 0; r < nameValues.numRows(); ++r) {
        const auto& nameType = nameValues.value(r, typeAttr);
        const auto& nameLang = nameValues.value(r, langAttr);
        const auto& name = nameValues.value(r, nameAttr);
        if (nameType == NAME_TYPE_OFFICIAL) {
            feedObjectData.addName({nameLang, name});
        } else if (nameType == NAME_TYPE_RENDER_LABEL) {
            feedObjectData.addShortName({nameLang, name});
        }
    }
}

struct SpravPoiObserverContextData : public Observer::ContextData
{
    std::vector<FeedObjectData> feedObjectDatas;
};

} // namespace

std::optional<poi_feed::FeedObjectData>
createSpravPoiSignal(const GeoObject* object, const maps::chrono::TimePoint& timePoint)
{
    auto permalinkIdAttrValue = object->attributes().value(ATTR_POI_BUSINESS_ID);
    if (permalinkIdAttrValue.empty()) {
        return std::nullopt;
    }
    FeedObjectData feedObjectData;
    feedObjectData.setNmapsId(object->id());
    feedObjectData.setPermalink(
        boost::lexical_cast<PermalinkId>(permalinkIdAttrValue));
    feedObjectData.setActualizationDate(timePoint);
    fillNames(object, feedObjectData);
    feedObjectData.setPosition(getPosition(object));
    feedObjectData.setRubricId(
        boost::lexical_cast<RubricId>(
            object->attributes().value(ATTR_POI_BUSINESS_RUBRIC_ID)));
    feedObjectData.setFtTypeId(getFtTypeId(object));
    feedObjectData.setIsGeoproduct(
        object->attributes().value(ATTR_POI_IS_GEOPRODUCT) == TRUE_VALUE
        ? FeedObjectData::IsGeoproduct::Yes
        : FeedObjectData::IsGeoproduct::No);
    return feedObjectData;
}

Observer::ContextDataPtr
SpravPoiObserver::beforeCommit(
    ObjectsCache& cache,
    const GeoObjectCollection&,
    UserContext&,
    const CommitContext&) const
{
    const auto& commit = cache.savedCommit();
    if (commit.createdBy() != maps::wiki::common::WIKIMAPS_SPRAV_UID &&
        commit.createdBy() != maps::wiki::common::WIKIMAPS_SPRAV_COMMON_UID)
    {
        return {};
    }
    auto spravPoiObserverContextData = std::make_shared<SpravPoiObserverContextData>();
    cache.find(
        [&](const GeoObject* object) {
            if (!object->isCreated()) {
                return false;
            }
            auto spravData = createSpravPoiSignal(object, commit.createdAtTimePoint());
            if (!spravData) {
                return false;
            }
            spravPoiObserverContextData->feedObjectDatas.push_back(*spravData);
            return false;
        }
    );
    if (spravPoiObserverContextData->feedObjectDatas.empty()) {
        return {};
    }
    return spravPoiObserverContextData;
}

void
SpravPoiObserver::afterCommit(TBranchId, Observer::ContextData& contextData) const
{
    auto spravPoiObserverContextData = dynamic_cast<const SpravPoiObserverContextData*>(&contextData);
    REQUIRE(spravPoiObserverContextData, "Invalid context data type");
    if (spravPoiObserverContextData->feedObjectDatas.empty()) {
        return;
    }
    auto workSocial = cfg()->poolSocial().masterWriteableTransaction();
    std::string query =
        "INSERT INTO " + MERGE_POI_RECENT_PATCHES_TABLE +
            " (" + OBJECT_ID_FIELD + ", " + DATA_JSON_FIELD + ") VALUES " +
            common::join(spravPoiObserverContextData->feedObjectDatas,
        [&](const auto& feedObjectData) {
            return
                "(" + std::to_string(feedObjectData.nmapsId()) +
                ", " + workSocial->quote(feedObjectData.toJson()) + ")";
        },
        ',');
    workSocial->exec(query);
    workSocial->commit();
}

} // namespace maps::wiki
