#include "save_role.h"

#include "common.h"
#include "magic_strings.h"
#include <maps/infra/yacare/include/error.h>
#include <yandex/maps/wiki/common/json_helpers.h>
#include <util/string/cast.h>

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

namespace maps::wiki::aclsrv {

namespace {

void
descend(
    std::vector<acl::SubjectPath>& result,
    const std::string& parentPath,
    const json::Value& jsonValue)
{
    if (!jsonValue.isObject() || jsonValue.fields().empty()) {
        result.push_back(acl::SubjectPath(parentPath));
        return;
    }
    for (const auto& fieldName : jsonValue.fields()) {
        std::string thisPath = parentPath;
        if (!thisPath.empty()) {
            thisPath += "/";
        }
        thisPath += dbPermissionId(fieldName);
        descend(result, thisPath, jsonValue[fieldName]);
    }
}

std::unordered_set<acl::ID>
readIdsSet(const json::Value& array)
{
    REQUIRE(array.isArray(), yacare::errors::BadRequest() << "Json array of ids expected.");
    std::unordered_set<acl::ID> result;
    result.reserve(array.size());
    for (const auto& idString : array) {
        acl::ID id = 0;
        REQUIRE(TryFromString(idString.as<std::string>(), id) && id > 0,
                yacare::errors::BadRequest() << "Invalid id");
        result.insert(id);
    }
    return result;
}

} // namespace

std::vector<acl::SubjectPath>
extractPermissions(const json::Value& jsonPermissionsObject)
{
    std::vector<acl::SubjectPath> result;
    if (jsonPermissionsObject.empty()) {
        return result;
    }
    descend(result, "", jsonPermissionsObject);
    return result;
}

RoleData::RoleData(const json::Value& jsonRoleObject)
    : id(common::readOptionalField<acl::ID>(jsonRoleObject, ID, 0))
{
    if (jsonRoleObject.hasField(NAME)) {
        name = boost::trim_copy(jsonRoleObject[NAME].as<std::string>());
    }
    if (jsonRoleObject.hasField(DESCRIPTION)) {
        description = boost::trim_copy(jsonRoleObject[DESCRIPTION].as<std::string>());
    }
    if (jsonRoleObject.hasField(PUBLIC)) {
        privacy = jsonRoleObject[PUBLIC].as<bool>()
            ? acl::Role::Privacy::Public
            : acl::Role::Privacy::Private;
    }
    if (jsonRoleObject.hasField(PERMISSIONS)) {
        permissionPaths = extractPermissions(jsonRoleObject[PERMISSIONS]);
    }
    if (jsonRoleObject.hasField(CAN_ASSIGN)) {
        if (jsonRoleObject[CAN_ASSIGN].hasField(ROLES)) {
            canAssignRolesIds = readIdsSet(jsonRoleObject[CAN_ASSIGN][ROLES]);
        }
        if (jsonRoleObject[CAN_ASSIGN].hasField(GROUPS)) {
            canAssignGroupsIds = readIdsSet(jsonRoleObject[CAN_ASSIGN][GROUPS]);
        }
    }
}

namespace {
void
updateCanAssign(acl::ACLGateway& gw, acl::Role& role, const RoleData& roleData)
{
    if (roleData.canAssignGroupsIds) {
        std::unordered_set<acl::ID> curIds;
        std::unordered_set<acl::ID> removeIds;
        for (const auto& canAssignGroup : role.canAssignGroups()) {
            const auto id = canAssignGroup.id();
            curIds.insert(id);
            if (!roleData.canAssignGroupsIds->count(id)) {
                removeIds.insert(id);
            }
        }
        std::unordered_set<acl::ID> addIds;
        for (const auto id : *roleData.canAssignGroupsIds) {
            if (!curIds.count(id)) {
                addIds.insert(id);
            }
        }
        for (const auto addId : addIds) {
            role.addCanAssignGroup(gw.group(addId));
        }
        for (const auto removeId : removeIds) {
            role.removeCanAssignGroup(gw.group(removeId));
        }
    }
    if (roleData.canAssignRolesIds) {
        std::unordered_set<acl::ID> curIds;
        std::unordered_set<acl::ID> removeIds;
        for (const auto& canAssignRole : role.canAssignRoles()) {
            const auto id = canAssignRole.id();
            curIds.insert(id);
            if (!roleData.canAssignRolesIds->count(id)) {
                removeIds.insert(id);
            }
        }
        std::unordered_set<acl::ID> addIds;
        for (const auto id : *roleData.canAssignRolesIds) {
            if (!curIds.count(id)) {
                addIds.insert(id);
            }
        }
        for (const auto addId : addIds) {
            role.addCanAssignRole(gw.role(addId));
        }
        for (const auto removeId : removeIds) {
            role.removeCanAssignRole(gw.role(removeId));
        }
    }
}
} // namespace

RoleSaveResult
saveRole(
    acl::ACLGateway& gw,
    const acl::Permissions& allPermissions,
    const RoleData& roleData,
    acl::UID /*authorUid*/)
{
    RoleSaveResult saveResult {
        roleData.id
            ? gw.role(roleData.id)
            : gw.createRole(
                roleData.name ? *roleData.name : std::string(),
                roleData.description ? *roleData.description : std::string(),
                roleData.privacy ? *roleData.privacy : acl::Role::Privacy::Private),
        !roleData.id
            ? IsNew::True
            : IsNew::False};
    auto& role = saveResult.role;
    if (roleData.id) {
        if (roleData.name && roleData.name != role.name()) {
            role.setName(*roleData.name);
        }
        if (roleData.description &&
            roleData.description != role.description())
        {
            role.setDescription(*roleData.description);
        }
        if (roleData.privacy && roleData.privacy != role.privacy()) {
            role.setPrivacy(*roleData.privacy);
        }
    }
    if (roleData.canAssignRolesIds || roleData.canAssignGroupsIds) {
        updateCanAssign(gw, role, roleData);
    }
    if (!roleData.permissionPaths) {
        return saveResult;
    }
    std::vector<acl::Permission> permissions;
    permissions.reserve(roleData.permissionPaths->size());
    for (const auto& permPath : *roleData.permissionPaths) {
        permissions.push_back(allPermissions.permission(permPath.pathParts()));
    }
    saveResult.changedPermissions =
        role.setPermissions(permissions)
            ? ChangedPermissions::True
            : ChangedPermissions::False;
    return saveResult;
}

} // namespace maps::wiki::aclsrv
