#include "module.h"
#include "poi_categories.h"
#include "indoor_poi_categories.h"

#include <yandex/maps/wiki/validator/check.h>

namespace maps {
namespace wiki {
namespace validator {
namespace checks {

using namespace categories;

namespace {
const std::string IMPORT_SOURCE_EXTPOIEXPORT = "extpoiexport";

using StringToIds = std::unordered_map<std::string, std::vector<TId>>;

template<class CategoryName>
bool collectPoisByCat(
    CheckContext* context,
    StringToIds& poiIdsByBusinessId,
    const std::optional<std::unordered_set<TId>>& levelIds)
{
    context->objects<CategoryName>().visit([&](const Poi* poi) {
        if (poi->parent() && levelIds && !levelIds->contains(poi->parent())) {
            return;
        }
        const auto& businessId = poi->businessId();
        if (businessId.empty()) {
            if (poi->importSource() == IMPORT_SOURCE_EXTPOIEXPORT) {
                context->fatal(
                    "missing-extpoiexport-business-id",
                    boost::none,
                    {poi->id()});
            }
            return;
        }
        poiIdsByBusinessId[businessId].push_back(poi->id());
    });
    return true;
}

template<class... Categories>
void collectPois(CheckContext* context, StringToIds& poiIdsByBusinessId, const std::optional<std::unordered_set<TId>>& levelIds = {})
{
    auto performed = {collectPoisByCat<Categories>(context, poiIdsByBusinessId, levelIds)...};
    (void)performed;
}

std::unordered_set<TId>
collectOperatingLevels(CheckContext* context)
{
    std::unordered_set<TId> levelIds;
    context->objects<INDOOR_LEVEL>().visit([&](const IndoorLevel* level) {
        const auto planId = level->parent();
        if (!context->objects<INDOOR_PLAN>().loaded(planId)) {
            return;
        }
        const auto plan = context->objects<INDOOR_PLAN>().byId(planId);
        if (!plan->isNotOperating()) {
            levelIds.insert(level->id());
        }
    });
    return levelIds;
}


} // namespace

VALIDATOR_SIMPLE_CHECK( poi_business_id, POI_CATEGORIES )
{
    StringToIds poiIdsByBusinessId;
    collectPois<POI_CATEGORIES>(context, poiIdsByBusinessId);

    for (const auto& pair : poiIdsByBusinessId) {
        const auto& poiIds = pair.second;
        if (poiIds.size() > 1) {
            context->error("duplicate-business-id", boost::none, poiIds);
        }
    }
}

VALIDATOR_SIMPLE_CHECK( indoor_poi_business_id, INDOOR_POI_CATEGORIES )
{
    StringToIds poiIdsByBusinessId;
    collectPois<INDOOR_POI_CATEGORIES>(context, poiIdsByBusinessId);

    for (const auto& [_, poiIds] : poiIdsByBusinessId) {
        if (poiIds.size() > 1) {
            context->critical("duplicate-business-id", boost::none, poiIds);
        }
    }
}

VALIDATOR_SIMPLE_CHECK( poi_business_id_indoor_share, POI_CATEGORIES, INDOOR_POI_CATEGORIES, INDOOR_PLAN, INDOOR_LEVEL)
{
    StringToIds poiIdsByBusinessId;
    collectPois<POI_CATEGORIES>(context, poiIdsByBusinessId);
    StringToIds indoorPoiIdsByBusinessId;
    const auto levels = collectOperatingLevels(context);
    collectPois<INDOOR_POI_CATEGORIES>(context, indoorPoiIdsByBusinessId, levels);

    for (const auto& [businessId, poiIds] : poiIdsByBusinessId) {
        const auto indoorIt = indoorPoiIdsByBusinessId.find(businessId);
        if (indoorIt == indoorPoiIdsByBusinessId.end()) {
            continue;
        }
        const auto& indoorPoiIds = indoorIt->second;
        auto errorIds = poiIds;
        errorIds.insert(errorIds.end(), indoorPoiIds.begin(), indoorPoiIds.end());
        context->error("poi-share-indoor-business-id", boost::none, errorIds);
    }
}

} // namespace checks
} // namespace validator
} // namespace wiki
} // namespace maps
