#include "rubric_matcher.h"

#include <maps/libs/xml/include/xml.h>
#include <yandex/maps/wiki/configs/editor/attrdef.h>

#include <library/cpp/resource/resource.h>

namespace maps::wiki::importer {

namespace {
const std::string RUBRICS_TO_FT_TYPE_RESOURCE_KEY = "/wiki-import-worker-matcher";
}

namespace {
const std::map<std::string, std::vector<RubricId>> CATEGORY_TO_RUBRIC {
    {"indoor_poi_info", {
        9,
        35,
        38,
        44,
        45
    }},
    {"indoor_poi_infra", {
        1,
        2,
        3,
        5,
        8,
        14,
        15,
        16,
        25,
        31,
        37,
        43,
        46
    }},
    {"indoor_poi_service", {
        4,
        7,
        10,
        13,
        18,
        19,
        20,
        23,
        24,
        26,
        27,
        32,
        36
    }},
    {"indoor_poi_finance", {
        34
    }},
    {"indoor_poi_medicine", {
        17
    }},
    {"indoor_poi_leisure", {
        12,
        22,
        28,
        29,
        30,
        33
    }},
    {"indoor_poi_sport", {
        21
    }},
    {"indoor_poi_goverment", {
        39,
        40,
        41,
        42
    }}
};
}

namespace fields {
const std::string DEFAULT_RUBRIC_ID = "rubric_id";
const std::string FT_TYPE_ID = "ft_type_id";
}

RubricsMatcher::RubricsMatcher(const EditorConfig& editorConfig):
    editorConfig_(editorConfig)
{
    for (const auto& [categoryId, rubrics] : CATEGORY_TO_RUBRIC) {
        for (const auto& rubric : rubrics) {
            rubricToCategory_.emplace(rubric, categoryId);
        }
    }
    auto matcherContent = NResource::Find(RUBRICS_TO_FT_TYPE_RESOURCE_KEY);

    xml3::Doc doc(matcherContent, xml3::Doc::Source::String);
    auto avaliableCategoryNodes = doc.nodes("/import/avaliable-categories/category");
    for (size_t i = 0; i < avaliableCategoryNodes.size(); ++i) {
        auto categoryId = avaliableCategoryNodes[i].value<std::string>();
        if (editorConfig_.hasCategory(categoryId)) {
            const auto& category = editorConfig_.category(categoryId);
            if (category.isAttributeDefined(category.id() + ":" + fields::FT_TYPE_ID)) {
                const auto& ftTypes = category.attribute(category.id() + ":" + fields::FT_TYPE_ID)->values();
                for (const auto& ftType : ftTypes) {
                    ftTypeToCategory_.emplace(ftType.value, categoryId);
                }
            }
        }
        avaliableCategoryIds_.insert(std::move(categoryId));
    }
    auto matchNodes = doc.nodes("/import/matches/match");
    for (size_t i = 0; i < matchNodes.size(); ++i) {
        std::string matchField = fields::DEFAULT_RUBRIC_ID;
        for (auto& name : matchNodes[i].attrNames()) {
            if (name != fields::FT_TYPE_ID) {
                matchField = name;
            }
        }
        auto fttypeId = matchNodes[i].attr<std::string>(fields::FT_TYPE_ID);
        auto rubricId = matchNodes[i].attr<std::string>(matchField);
        rubricTypeRelationMap_[matchField].emplace(std::move(rubricId), std::move(fttypeId));
    }
}

std::string RubricsMatcher::rubricToCategory(
    const std::string& rubric) const
{
    const auto& rubricIt = rubricToCategory_.find(cast<RubricId>(rubric));
    if (rubricIt != rubricToCategory_.end()) {
        return rubricIt->second;
    }
    auto ftType = editorConfig_.rubrics().defaultFtType(
        boost::lexical_cast<configs::editor::RubricId>(rubric));
    REQUIRE(ftType, "There is no ftType for rubric: " << rubric);

    const auto& categoryIt = ftTypeToCategory_.find(std::to_string(*ftType));
    REQUIRE(categoryIt != ftTypeToCategory_.end(), "There is no category for ft_type: " << *ftType );
    return categoryIt->second;
}

bool RubricsMatcher::isCategoryAvaliable(
    const std::string& categoryId) const
{
    return avaliableCategoryIds_.count(categoryId) > 0;
}

bool RubricsMatcher::isFieldNameMatched(
    const std::string& categoryId,
    const std::string& rubricType) const
{
    if (!isCategoryAvaliable(categoryId)) {
        return false;
    }
    return rubricTypeRelationMap_.find(rubricType) != rubricTypeRelationMap_.end() || rubricType == fields::DEFAULT_RUBRIC_ID;
}

std::optional<std::string> RubricsMatcher::getFtType(
    const std::string& rubricType,
    const std::string& rubricId) const
{
    if (rubricType == fields::DEFAULT_RUBRIC_ID) {
        auto ftType = editorConfig_.rubrics().defaultFtType(
            boost::lexical_cast<configs::editor::RubricId>(rubricId));
        return ftType
            ? std::optional<std::string>(std::to_string(*ftType))
            : std::nullopt;
    }
    auto rubricTypeIt = rubricTypeRelationMap_.find(rubricType);
    if (rubricTypeIt == rubricTypeRelationMap_.end()) {
        return std::nullopt;
    }
    const auto& rubrics = rubricTypeIt->second;
    auto it = rubrics.find(rubricId);
    if (it == rubrics.end()) {
        return std::nullopt;
    }
    return it->second;
}

} // maps::wiki::importer
