#include "poi.h"
#include "../checks/magic_string.h"
#include "../checks/utils.h"

#include "../message_reporter.h"
#include "../geom_index.h"

#include <yandex/maps/wiki/diffalert/revision/snapshot.h>
#include <yandex/maps/wiki/diffalert/revision/diff_context.h>

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

#include <initializer_list>

namespace maps::wiki::diffalert {

namespace {

const std::string IMPORTANT_PREFIX = "important-poi-";
const std::string REGULAR_PREFIX = "poi-";

} // namespace

void checkPoiInAreaOfModel3D(const LongtaskDiffContext& diff, MessageReporter& messages)
{
    if (!(isPoi(diff.categoryId()))) {
        return;
    }
    const auto responsibilityGroup = teamResponsibilityGroup(diff);
    if (responsibilityGroup != TeamResponsibilityGroup::Nmaps) {
        return;
    }
    auto isInAreaOfModel3D = [&](LongtaskSnapshot& snapshot, const Object& object)
    {
        if (!object.geom().isNull()) {
            const TIds bldIds = snapshot.bldWith3Dmodel().objectIdsByEnvelope(
                *object.geom()->getEnvelopeInternal()
            );
            for (const auto& bld: snapshot.objectsByIds(bldIds)) {
                if (bld->geom().isNull()) {
                    continue;
                }
                if (bld->geom()->contains(object.geom().geosGeometryPtr())) {
                    return true;
                }
            }
        }

        return false;
    };

    if (!diff.newObject()) {
        if (isInAreaOfModel3D(diff.oldSnapshot(), *diff.oldObject())) {
            messages.report(
                prioByRespGroup({3, 4}, responsibilityGroup),
                makePoiMessage("poi-in-area-of-model3d-deleted", diff, RunMode::LongTask));
        }
    } else if (!diff.oldObject()) {
        if (isInAreaOfModel3D(diff.newSnapshot(), *diff.newObject())) {
            messages.report(
                prioByRespGroup({3, 4}, responsibilityGroup),
                makePoiMessage("poi-in-area-of-model3d-created", diff, RunMode::LongTask));
        }
    } else {
        const bool oldPosition = isInAreaOfModel3D(diff.oldSnapshot(), *diff.oldObject());
        const bool newPosition = isInAreaOfModel3D(diff.newSnapshot(), *diff.newObject());
        if (oldPosition || newPosition) {
            messages.report(
                prioByRespGroup({3, 4}, responsibilityGroup),
                makePoiMessage("poi-in-area-of-model3d-changed", diff, RunMode::LongTask));
        }
    }
}


void checkImportantPoi(const DiffContext& diff, MessageReporter& messages)
{
    if (!(isPoi(diff.categoryId()) && !isAny(diff, isMajorPoi))) {
        return;
    }
    const auto responsibilityGroup = teamResponsibilityGroup(diff);
    auto report = [&messages, &responsibilityGroup, &diff](
            PoiImportance importance,
            const std::string& messageSuffix,
            Message::Scope scope)
    {
        const auto& prefix =
            importance == PoiImportance::None
            ? REGULAR_PREFIX
            : IMPORTANT_PREFIX;

        messages.report(
            prioByRespGroup(poiMessagePriority(importance), responsibilityGroup),
            makePoiMessage(prefix + messageSuffix, diff, RunMode::LongTask), scope);
    };

    if (!diff.oldObject()) {
        report(poiImportance(*diff.newObject()), "created", Message::Scope::WholeObject);
    } else if (!diff.newObject()) {
        report(poiImportance(*diff.oldObject()), "deleted", Message::Scope::WholeObject);
    } else {
        const auto importance = std::max(
            poiImportance(*diff.oldObject()),
            poiImportance(*diff.newObject())
        );
        if (diff.categoryChanged() &&
            responsibilityGroup == TeamResponsibilityGroup::Nmaps)
        {
            report(importance, "category-changed", Message::Scope::WholeObject);
        }
        if (diff.attrsChanged() || diff.tableAttrsChanged()) {
            report(importance, "attrs-changed", Message::Scope::WholeObject);
        }
        if (diff.geomChanged()) {
            const auto& oldGeom = diff.oldObject()->geom();
            const auto& newGeom = diff.newObject()->geom();
            const auto displacement = mercatorDistanceRatio(oldGeom) * oldGeom.distance(newGeom);
            if (displacement >= 100 || (isGeoproductPoi(diff) && displacement >= 40)) {
                report(importance, "displaced", Message::Scope::GeomDiff);
            } else if (common::isIn(importance,
                            { PoiImportance::ImportantDispClass5, PoiImportance::OtherDispClass5 })) {
                messages.report(
                    prioByRespGroup({3, 1}, responsibilityGroup),
                    makePoiMessage("important-poi-displaced", diff, RunMode::LongTask),
                    Message::Scope::GeomDiff);
            }
        }
    }
}

} // namespace maps::wiki::diffalert
