#include "acl_role_info.h"
#include "configs/config.h"

#include <maps/libs/common/include/exception.h>
#include <yandex/maps/wiki/common/moderation.h>
#include <yandex/maps/wiki/configs/editor/category_groups.h>

#include <boost/algorithm/string/split.hpp>

namespace maps::wiki::moderation {

struct AclRoleNameParts
{
    std::string roleName;
    bool autoFlag = false;
    bool notFlag = false;
    StringSet categoryGroupIds;
};

namespace {

const StringSet ACL_ROLES_WITH_CATEGORY_GROUPS = {
    common::MODERATION_STATUS_EXPERT,
    common::MODERATION_STATUS_AUTO_APPROVE_EDIT,
    common::MODERATION_STATUS_MODERATOR,
    common::MODERATION_STATUS_AUTO_APPROVE_MODERATOR,
    common::MODERATION_STATUS_YANDEX_MODERATOR,
    common::MODERATION_STATUS_CARTOGRAPHER
};

// Splits the ACL role name in form of:
//   <role-name>[.[auto.][not.]<task_or_cat_group>. ...]
// where 'task_or_cat_group' is equal to the name of a group from
// `category_groups.xml` or 'common' for common tasks (tasks
// without category, for example a group move).
//
// There is separate case when 'feedback_moderation' string may stand
// directly after <role-name>. It means that user has rights to moderate
// feedback tasks.
// No keywords like 'auto', 'not' or [cat_group] are allowed for role if
// 'feedback_moderation' is present.
//
// @return
//   - the role name;
//   - the auto-flag;
//   - the not-flag; and
//   - category groups.
//   - the feedback_moderation flag
//
AclRoleNameParts splitAclRoleName(const std::string& aclRoleName)
{
    REQUIRE(!aclRoleName.empty(), "An ACL role name can't be empty.");

    std::list<std::string> nameParts;
    boost::algorithm::split(nameParts, aclRoleName,
        [](char c) {
            return c == common::MODERATION_STATUS_DELIMITER;
        });

    AclRoleNameParts result;

    result.roleName = std::move(nameParts.front());
    nameParts.pop_front();

    if (!nameParts.empty() && nameParts.front() == AUTO_FLAG) {
        REQUIRE(result.roleName == common::MODERATION_STATUS_EXPERT,
            "'" << AUTO_FLAG << "' can be applied to the expert role only.");

        nameParts.pop_front();
        result.autoFlag = true;
    }

    if (!nameParts.empty() && nameParts.front() == NOT_FLAG) {
        nameParts.pop_front();
        result.notFlag = true;
        REQUIRE(!nameParts.empty(),
            "'" << NOT_FLAG << "' can't be the last part of the ACL role: '" << aclRoleName << "'.");
    }

    result.categoryGroupIds = StringSet(
        std::make_move_iterator(nameParts.begin()),
        std::make_move_iterator(nameParts.end())
    );

    return result;
}

} // namespace

AclRoleInfo::AclRoleInfo(std::string aclRoleName):
    aclRoleName_(std::move(aclRoleName))
{
    auto aclRoleNameParts = splitAclRoleName(this->aclRoleName_);
    roleName_ = aclRoleNameParts.roleName;
    isAuto_ = aclRoleNameParts.autoFlag;

    setTrustLevel(aclRoleNameParts);
    setCategoryGroupIds(aclRoleNameParts);
    setCanProcessAllGroups(aclRoleNameParts);
    setHasAccessToCommonTasks(aclRoleNameParts);
}

const std::string& AclRoleInfo::roleName() const
{
    return roleName_;
}

TrustLevel AclRoleInfo::trustLevel() const
{
    return trustLevel_;
}

const StringSet& AclRoleInfo::categoryGroupIds() const
{
    return categoryGroupIds_;
}

bool AclRoleInfo::canProcessAllGroups() const
{
    return canProcessAllGroups_;
}

bool AclRoleInfo::hasAccessToCommonTasks() const
{
    return hasAccessToCommonTasks_;
}

bool AclRoleInfo::isAuto() const
{
    return isAuto_;
}

bool AclRoleInfo::canProcessCategory(const std::string& categoryId) const
{
    const auto& categoryGroups = cfg()->editor()->categoryGroups();
    return canProcessAllGroups_ ||
        categoryGroups.categoryIdsByGroups(categoryGroupIds_).count(categoryId);
}

void AclRoleInfo::setTrustLevel(const AclRoleNameParts& aclRoleNameParts)
{
    if (aclRoleNameParts.roleName == common::MODERATION_STATUS_CARTOGRAPHER) {
        trustLevel_ = TrustLevel::SkipModeration;
    } else if (ACL_ROLES_WITH_CATEGORY_GROUPS.count(aclRoleNameParts.roleName)) {
        trustLevel_ = TrustLevel::AcceptAndModerate;
    } else {
        trustLevel_ = TrustLevel::NotTrusted;
    }
}

void AclRoleInfo::setCategoryGroupIds(const AclRoleNameParts& aclRoleNameParts)
{
    const auto& groupIds = aclRoleNameParts.categoryGroupIds;
    if (!ACL_ROLES_WITH_CATEGORY_GROUPS.count(aclRoleNameParts.roleName)) {
        REQUIRE(groupIds.empty(),
                "Category groups are disallowed for ACL role '" << aclRoleName_ << "'.");
    }
    for (const auto& groupId: groupIds) {
        REQUIRE(groupId == CATEGORY_GROUP_COMMON ||
                cfg()->editor()->categoryGroups().isGroupExists(groupId),
                "Unknown group: '" << groupId << "' in the ACL role: '" << aclRoleName_ << "'.");
    }
    if (!groupIds.empty() && !aclRoleNameParts.notFlag) {
        categoryGroupIds_ = groupIds;
        categoryGroupIds_.erase(CATEGORY_GROUP_COMMON);
        return;
    }
    for (const auto& groupId: cfg()->editor()->categoryGroups().allGroups()) {
        categoryGroupIds_.insert(groupId.first);
    }
    if (aclRoleNameParts.notFlag) {
        for (const auto& groupId: groupIds) {
            categoryGroupIds_.erase(groupId);
        }
    }
}

void AclRoleInfo::setCanProcessAllGroups(const AclRoleNameParts& aclRoleNameParts)
{
    canProcessAllGroups_ = false;
    if (trustLevel_ == TrustLevel::NotTrusted) {
        return;
    }

    const auto& groupIds = aclRoleNameParts.categoryGroupIds;
    if (groupIds.empty() || (groupIds.size() == 1 &&
        groupIds.count(CATEGORY_GROUP_COMMON) &&
        aclRoleNameParts.notFlag)) {
            canProcessAllGroups_ = true;
    }
}

void AclRoleInfo::setHasAccessToCommonTasks(const AclRoleNameParts& aclRoleNameParts)
{
    if (trustLevel_ == TrustLevel::NotTrusted) {
        hasAccessToCommonTasks_ = false;
        return;
    }
    const auto& groupIds = aclRoleNameParts.categoryGroupIds;
    hasAccessToCommonTasks_ = groupIds.empty() || groupIds.count(CATEGORY_GROUP_COMMON);
    if (aclRoleNameParts.notFlag) {
        hasAccessToCommonTasks_ = !hasAccessToCommonTasks_;
    }
}

} // namespace maps::wiki::moderation
