package cz.muni.ics.oidc.server.adapters.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import cz.muni.ics.oidc.PerunConstants;
import cz.muni.ics.oidc.models.AttributeMapping;
import cz.muni.ics.oidc.models.Facility;
import cz.muni.ics.oidc.models.Group;
import cz.muni.ics.oidc.models.Member;
import cz.muni.ics.oidc.models.Model;
import cz.muni.ics.oidc.models.PerunAttribute;
import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.models.Resource;
import cz.muni.ics.oidc.models.UserExtSource;
import cz.muni.ics.oidc.models.Vo;
import cz.muni.ics.oidc.models.enums.MemberStatus;
import cz.muni.ics.oidc.models.enums.PerunEntityType;
import cz.muni.ics.oidc.models.mappers.RpcMapper;
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import cz.muni.ics.oidc.server.adapters.PerunAdapterMethods;
import cz.muni.ics.oidc.server.adapters.PerunAdapterMethodsRpc;
import cz.muni.ics.oidc.server.connectors.Affiliation;
import cz.muni.ics.oidc.server.connectors.PerunConnectorRpc;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static cz.muni.ics.oidc.models.PerunAttributeValue.STRING_TYPE;
import static cz.muni.ics.oidc.models.PerunAttributeValueAwareModel.ARRAY_TYPE;
import static cz.muni.ics.oidc.models.enums.MemberStatus.VALID;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.ATTRIBUTES_MANAGER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.EXT_SOURCE_IDP;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.FACILITIES_MANAGER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.GROUPS_MANAGER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.MEMBERS_MANAGER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ALLOWED_GROUPS;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ALLOWED_RESOURCES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_APPLICATION_FORM;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ASSIGNED_GROUPS;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ASSIGNED_RESOURCES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ASSIGNED_RICH_RESOURCES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ATTRIBUTE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ATTRIBUTES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ENTITYLESS_ATTRIBUTES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_ENTITYLESS_KEYS;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_FACILITIES_BY_ATTRIBUTE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_GROUPS_WHERE_USER_IS_ACTIVE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_GROUP_BY_ID;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_GROUP_BY_NAME;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_MEMBERS_BY_USER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_MEMBER_BY_USER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_MEMBER_GROUPS;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_RICH_GROUPS_ASSIGNED_TO_RESOURCE_WITH_ATTRIBUTES_BY_NAMES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_USER_BY_EXT_SOURCE_NAME_AND_EXT_LOGIN;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_USER_BY_ID;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_USER_EXT_SOURCES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_VO_BY_ID;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_GET_VO_BY_SHORT_NAME;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_IS_GROUP_MEMBER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.METHOD_SET_ATTRIBUTE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_ATTRIBUTE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_ATTRIBUTE_DEFINITION;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_ATTRIBUTE_NAME;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_ATTRIBUTE_VALUE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_ATTR_NAME;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_ATTR_NAMES;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_EXT_LOGIN;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_EXT_SOURCE_NAME;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_FACILITY;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_GROUP;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_ID;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_MEMBER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_NAME;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_RESOURCE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_SHORT_NAME;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_USER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_USER_EXT_SOURCE;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.PARAM_VO;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.REGISTRAR_MANAGER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.RESOURCES_MANAGER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.USERS_MANAGER;
import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.VOS_MANAGER;

/**
 * Interface for fetching data from Perun via RPC
 *
 * @author Martin Kuba makub@ics.muni.cz
 * @author Dominik František Bučík bucik@ics.muni.cz
 * @author Peter Jancus jancus@ics.muni.cz
 */
@Slf4j
public class PerunAdapterRpc
        extends PerunAdapterWithMappingServices
        implements PerunAdapterMethods, PerunAdapterMethodsRpc
{

    // FIELD NAMES

    protected static final String FIELD_ATTRIBUTES = "attributes";
    protected static final String FIELD_NAMESPACE = "namespace";
    protected static final String FIELD_FRIENDLY_NAME = "friendlyName";
    protected static final String FIELD_VALUE = "value";

    // OTHERS

    protected static final String ORG_URL = "orgUrl";
    protected static final String AFF_MAPPING = "affMapping";

    // VARIABLES

    private PerunConnectorRpc connectorRpc;
    private String oidcClientIdAttr;
    private String oidcCheckMembershipAttr;
    private String orgUrlAttr;
    private String affiliationsAttr;

    public void setConnectorRpc(PerunConnectorRpc connectorRpc) {
        this.connectorRpc = connectorRpc;
    }

    public void setOidcClientIdAttr(String oidcClientIdAttr) {
        this.oidcClientIdAttr = oidcClientIdAttr;
    }

    public void setOidcCheckMembershipAttr(String oidcCheckMembershipAttr) {
        this.oidcCheckMembershipAttr = oidcCheckMembershipAttr;
    }

    public void setOrgUrlAttr(String orgUrlAttr) {
        this.orgUrlAttr = orgUrlAttr;
    }

    public void setAffiliationsAttr(String affiliationsAttr) {
        this.affiliationsAttr = affiliationsAttr;
    }

    @Override
    public PerunUser getPreauthenticatedUserId(String extLogin, String extSourceName) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }
        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_EXT_LOGIN, extLogin);
        map.put(PARAM_EXT_SOURCE_NAME, extSourceName);

        JsonNode response = connectorRpc.post(USERS_MANAGER, METHOD_GET_USER_BY_EXT_SOURCE_NAME_AND_EXT_LOGIN, map);
        return RpcMapper.mapPerunUser(response);
    }

    @Override
    public Facility getFacilityByClientId(String clientId) {
        if (!connectorRpc.isEnabled()) {
            return null;
        } else if (!StringUtils.hasText(clientId)) {
            return null;
        }

        AttributeMapping mapping = getFacilityAttributesMappingService().getMappingByIdentifier(oidcClientIdAttr);

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_ATTRIBUTE_NAME, mapping.getRpcName());
        map.put(PARAM_ATTRIBUTE_VALUE, clientId);
        JsonNode jsonNode = connectorRpc.post(FACILITIES_MANAGER, METHOD_GET_FACILITIES_BY_ATTRIBUTE, map);

        return (jsonNode.size() > 0) ? RpcMapper.mapFacility(jsonNode.get(0)) : null;
    }

    @Override
    public boolean isMembershipCheckEnabledOnFacility(Facility facility) {
        if (!connectorRpc.isEnabled()) {
            return false;
        }

        AttributeMapping mapping = getFacilityAttributesMappingService().getMappingByIdentifier(oidcCheckMembershipAttr);

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_FACILITY, facility.getId());
        map.put(PARAM_ATTRIBUTE_NAME, mapping.getRpcName());
        JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, METHOD_GET_ATTRIBUTE, map);

        return res.get(FIELD_VALUE).asBoolean(false);
    }

    @Override
    public boolean canUserAccessBasedOnMembership(Facility facility, Long userId, String ignoreAttr) {
        if (!connectorRpc.isEnabled()) {
            return true;
        }

        Set<Group> activeGroups = getGroupsWhereUserIsActive(facility.getId(), userId, ignoreAttr);
        return !activeGroups.isEmpty();
    }

    @Override
    public Map<Vo, List<Group>> getGroupsForRegistration(Facility facility, Long userId, List<String> voShortNames) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        List<Vo> vos = getVosByShortNames(voShortNames);
        Map<Long, Vo> vosMap = convertVoListToMap(vos);
        List<Member> userMembers = getMembersByUser(userId);
        userMembers = new ArrayList<>(new HashSet<>(userMembers));

        //Filter out vos where member is other than valid or expired. These vos cannot be used for registration
        Map<Long, MemberStatus> memberVoStatuses = convertMembersListToStatusesMap(userMembers);
        Map<Long, Vo> vosForRegistration = new HashMap<>();
        for (Map.Entry<Long, Vo> entry : vosMap.entrySet()) {
            if (memberVoStatuses.containsKey(entry.getKey())) {
                MemberStatus status = memberVoStatuses.get(entry.getKey());
                if (VALID.equals(status) || MemberStatus.EXPIRED.equals(status)) {
                    vosForRegistration.put(entry.getKey(), entry.getValue());
                }
            } else {
                vosForRegistration.put(entry.getKey(), entry.getValue());
            }
        }

        // filter groups only if their VO is in the allowed VOs and if they have registration form
        List<Group> allowedGroups = getAllowedGroups(facility);
        List<Group> groupsForRegistration = allowedGroups.stream()
                .filter(group -> vosForRegistration.containsKey(group.getVoId()) && hasApplicationForm(group))
                .collect(Collectors.toList());

        // create map for processing
        Map<Vo, List<Group>> result = new HashMap<>();
        for (Group group : groupsForRegistration) {
            Vo vo = vosMap.get(group.getVoId());
            result.computeIfAbsent(vo, v -> new ArrayList<>());
            result.get(vo).add(group);
        }

        return result;
    }

    @Override
    public boolean groupWhereCanRegisterExists(Facility facility) {
        if (!connectorRpc.isEnabled()) {
            return false;
        }

        List<Group> allowedGroups = getAllowedGroups(facility);

        if (!allowedGroups.isEmpty()) {
            for (Group group : allowedGroups) {
                if (hasApplicationForm(group)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean isUserInGroup(Long userId, Long groupId) {
        if (!connectorRpc.isEnabled()) {
            return false;
        }

        Map<String, Object> groupParams = new LinkedHashMap<>();
        groupParams.put(PARAM_ID, groupId);
        JsonNode groupResponse = connectorRpc.post(GROUPS_MANAGER, METHOD_GET_GROUP_BY_ID, groupParams);
        Group group = RpcMapper.mapGroup(groupResponse);

        Map<String, Object> memberParams = new LinkedHashMap<>();
        memberParams.put(PARAM_VO, group.getVoId());
        memberParams.put(PARAM_USER, userId);
        JsonNode memberResponse = connectorRpc.post(MEMBERS_MANAGER, METHOD_GET_MEMBER_BY_USER, memberParams);
        Member member = RpcMapper.mapMember(memberResponse);

        Map<String, Object> isGroupMemberParams = new LinkedHashMap<>();
        isGroupMemberParams.put(PARAM_GROUP, groupId);
        isGroupMemberParams.put(PARAM_MEMBER, member.getId());
        JsonNode res = connectorRpc.post(GROUPS_MANAGER, METHOD_IS_GROUP_MEMBER, isGroupMemberParams);

        return res.asBoolean(false);
    }

    @Override
    public boolean setUserAttribute(Long userId, PerunAttribute attribute) {
        if (!connectorRpc.isEnabled()) {
            return true;
        }

        JsonNode attributeJson = attribute.toJson();

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_USER, userId);
        map.put(PARAM_ATTRIBUTE, attributeJson);

        JsonNode response = connectorRpc.post(ATTRIBUTES_MANAGER, METHOD_SET_ATTRIBUTE, map);
        return (response == null || response.isNull() || response instanceof NullNode);
    }

    @Override
    public List<Affiliation> getUserExtSourcesAffiliations(Long userId) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        List<UserExtSource> userExtSources = getUserExtSources(userId);
        List<Affiliation> affiliations = new ArrayList<>();

        AttributeMapping affMapping = new AttributeMapping(AFF_MAPPING, affiliationsAttr, "", ARRAY_TYPE);
        AttributeMapping orgUrlMapping = new AttributeMapping(ORG_URL, orgUrlAttr, "", STRING_TYPE);
        Set<AttributeMapping> attributeMappings = new HashSet<>(Arrays.asList(affMapping, orgUrlMapping));

        for (UserExtSource ues : userExtSources) {
            if (EXT_SOURCE_IDP.equals(ues.getExtSource().getType())) {
                Map<String, PerunAttributeValue> uesAttrValues = getUserExtSourceAttributeValues(ues.getId(), attributeMappings);

                long asserted = ues.getLastAccess().getTime() / 1000L;

                String orgUrl = uesAttrValues.get(orgUrlMapping.getIdentifier()).valueAsString();
                String affs = uesAttrValues.get(affMapping.getIdentifier()).valueAsString();
                if (affs != null) {
                    for (String aff : affs.split(";")) {
                        String source = ((orgUrl != null) ? orgUrl : ues.getExtSource().getName());
                        Affiliation affiliation = new Affiliation(source, aff, asserted);
                        log.debug("found {} from IdP {} with orgURL {} asserted at {}", aff, ues.getExtSource().getName(),
                                orgUrl, asserted);
                        affiliations.add(affiliation);
                    }
                }
            }
        }

        return affiliations;
    }

    @Override
    public List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        List<Affiliation> affiliations = new ArrayList<>();

        List<Member> userMembers = getMembersByUser(userId);
        for (Member member : userMembers) {
            if (VALID.equals(member.getStatus())) {
                List<Group> memberGroups = getMemberGroups(member.getId());
                for (Group group : memberGroups) {
                    PerunAttributeValue attrValue = getGroupAttributeValue(group, groupAffiliationsAttr);
                    if (attrValue != null && attrValue.valueAsString() != null) {
                        long linuxTime = System.currentTimeMillis() / 1000L;
                        for (String value : attrValue.valueAsList()) {
                            Affiliation affiliation = new Affiliation(null, value, linuxTime);
                            log.debug("found {} on group {}", value, group.getName());
                            affiliations.add(affiliation);
                        }
                    }
                }
            }
        }

        return affiliations;
    }

    @Override
    public List<String> getGroupsAssignedToResourcesWithUniqueNames(Facility facility) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        List<Resource> resources = getAssignedResources(facility);
        List<String> result = new ArrayList<>();

        String voShortName = "urn:perun:group:attribute-def:virt:voShortName";

        for (Resource res : resources) {
            List<Group> groups = getRichGroupsAssignedToResourceWithAttributesByNames(res, Collections.singletonList(voShortName));

            for (Group group : groups) {
                if (group.getAttributeByUrnName(voShortName) != null &&
                        group.getAttributeByUrnName(voShortName).hasNonNull(FIELD_VALUE)) {
                    String value = group.getAttributeByUrnName(voShortName).get(FIELD_VALUE).textValue();
                    group.setUniqueGroupName(value + ":" + group.getName());
                    result.add(group.getUniqueGroupName());
                }
            }
        }

        return result;
    }

    @Override
    public Map<String, PerunAttribute> getEntitylessAttributes(String attributeName) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, Object> attrNameMap = new LinkedHashMap<>();
        attrNameMap.put(PARAM_ATTR_NAME, attributeName);
        JsonNode entitylessAttributesJson = connectorRpc.post(ATTRIBUTES_MANAGER, METHOD_GET_ENTITYLESS_ATTRIBUTES, attrNameMap);

        Long attributeDefinitionId = RpcMapper.mapAttribute(entitylessAttributesJson.get(0)).getId();

        Map<String, Object> attributeDefinitionIdMap = new LinkedHashMap<>();
        attributeDefinitionIdMap.put(PARAM_ATTRIBUTE_DEFINITION, attributeDefinitionId);
        JsonNode entitylessKeysJson = connectorRpc.post(ATTRIBUTES_MANAGER, METHOD_GET_ENTITYLESS_KEYS, attributeDefinitionIdMap);

        Map<String, PerunAttribute> result = new LinkedHashMap<>();

        for (int i = 0; i < entitylessKeysJson.size(); i++) {
            result.put(entitylessKeysJson.get(i).asText(), RpcMapper.mapAttribute(entitylessAttributesJson.get(i)));
        }

        return result;
    }

    @Override
    public Vo getVoByShortName(String shortName) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        Map<String, Object> params = new LinkedHashMap<>();
        params.put(PARAM_SHORT_NAME, shortName);

        JsonNode jsonNode = connectorRpc.post(VOS_MANAGER, METHOD_GET_VO_BY_SHORT_NAME, params);
        return RpcMapper.mapVo(jsonNode);
    }

    @Override
    public Map<String, PerunAttributeValue> getUserAttributeValues(PerunUser user, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getUserAttributeValues(user.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttributeValue> getUserAttributeValues(Long userId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, PerunAttribute> userAttributes = getUserAttributes(userId, attrsToFetch);
        return extractValues(userAttributes);
    }

    @Override
    public PerunAttributeValue getUserAttributeValue(PerunUser user, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getUserAttributeValue(user.getId(), attrToFetch);
    }

    @Override
    public PerunAttributeValue getUserAttributeValue(Long userId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getUserAttribute(userId, attrToFetch).toPerunAttributeValue();
    }

    @Override
    public Map<String, PerunAttributeValue> getFacilityAttributeValues(Facility facility, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getFacilityAttributeValues(facility.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttributeValue> getFacilityAttributeValues(Long facilityId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, PerunAttribute> facilityAttributes = getFacilityAttributes(facilityId, attrsToFetch);
        return extractValues(facilityAttributes);
    }

    @Override
    public PerunAttributeValue getFacilityAttributeValue(Facility facility, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getFacilityAttributeValue(facility.getId(), attrToFetch);
    }

    @Override
    public PerunAttributeValue getFacilityAttributeValue(Long facilityId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getFacilityAttribute(facilityId, attrToFetch).toPerunAttributeValue();
    }

    @Override
    public Map<String, PerunAttributeValue> getVoAttributeValues(Vo vo, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getVoAttributeValues(vo.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttributeValue> getVoAttributeValues(Long voId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, PerunAttribute> voAttributes = getVoAttributes(voId, attrsToFetch);
        return extractValues(voAttributes);
    }

    @Override
    public PerunAttributeValue getVoAttributeValue(Vo vo, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getVoAttributeValue(vo.getId(), attrToFetch);
    }

    @Override
    public PerunAttributeValue getVoAttributeValue(Long voId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getVoAttribute(voId, attrToFetch).toPerunAttributeValue();
    }

    @Override
    public Map<String, PerunAttributeValue> getGroupAttributeValues(Group group, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getGroupAttributeValues(group.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttributeValue> getGroupAttributeValues(Long groupId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, PerunAttribute> groupAttributes = getGroupAttributes(groupId, attrsToFetch);
        return extractValues(groupAttributes);
    }

    @Override
    public PerunAttributeValue getGroupAttributeValue(Group group, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getGroupAttributeValue(group.getId(), attrToFetch);
    }

    @Override
    public PerunAttributeValue getGroupAttributeValue(Long groupId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getGroupAttribute(groupId, attrToFetch).toPerunAttributeValue();
    }

    @Override
    public Map<String, PerunAttributeValue> getResourceAttributeValues(Resource resource, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getResourceAttributeValues(resource.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttributeValue> getResourceAttributeValues(Long resourceId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, PerunAttribute> resourceAttributes = getResourceAttributes(resourceId, attrsToFetch);
        return extractValues(resourceAttributes);
    }

    @Override
    public PerunAttributeValue getResourceAttributeValue(Resource resource, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getResourceAttributeValue(resource.getId(), attrToFetch);
    }

    @Override
    public PerunAttributeValue getResourceAttributeValue(Long resourceId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getResourceAttribute(resourceId, attrToFetch).toPerunAttributeValue();
    }

    @Override
    public Map<String, PerunAttribute> getFacilityAttributes(Facility facility, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getFacilityAttributes(facility.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getFacilityAttributes(Long facilityId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getAttributes(PerunEntityType.FACILITY, facilityId, attrsToFetch);
    }

    @Override
    public PerunAttribute getFacilityAttribute(Facility facility, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getFacilityAttribute(facility.getId(), attrToFetch);
    }

    @Override
    public PerunAttribute getFacilityAttribute(Long facilityId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getAttribute(PerunEntityType.FACILITY, facilityId, attrToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getGroupAttributes(Group group, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getGroupAttributes(group.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getGroupAttributes(Long groupId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getAttributes(PerunEntityType.GROUP, groupId, attrsToFetch);
    }

    @Override
    public PerunAttribute getGroupAttribute(Group group, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getGroupAttribute(group.getId(), attrToFetch);
    }

    @Override
    public PerunAttribute getGroupAttribute(Long groupId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getAttribute(PerunEntityType.GROUP, groupId, attrToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getUserAttributes(PerunUser user, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getUserAttributes(user.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getUserAttributes(Long userId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getAttributes(PerunEntityType.USER, userId, attrsToFetch);
    }

    @Override
    public PerunAttribute getUserAttribute(PerunUser user, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getUserAttribute(user.getId(), attrToFetch);
    }

    @Override
    public PerunAttribute getUserAttribute(Long userId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getAttribute(PerunEntityType.USER, userId, attrToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getVoAttributes(Vo vo, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getVoAttributes(vo.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getVoAttributes(Long voId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getAttributes(PerunEntityType.VO, voId, attrsToFetch);
    }

    @Override
    public PerunAttribute getVoAttribute(Vo vo, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getVoAttribute(vo.getId(), attrToFetch);
    }

    @Override
    public PerunAttribute getVoAttribute(Long voId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getAttribute(PerunEntityType.VO, voId, attrToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getResourceAttributes(Resource resource, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getResourceAttributes(resource.getId(), attrsToFetch);
    }

    @Override
    public Map<String, PerunAttribute> getResourceAttributes(Long resourceId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        return getAttributes(PerunEntityType.RESOURCE, resourceId, attrsToFetch);
    }

    @Override
    public PerunAttribute getResourceAttribute(Resource resource, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getResourceAttribute(resource.getId(), attrToFetch);
    }

    @Override
    public PerunAttribute getResourceAttribute(Long resourceId, String attrToFetch) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        return getAttribute(PerunEntityType.RESOURCE, resourceId, attrToFetch);
    }

    @Override
    public Set<String> getCapabilities(Facility facility, Set<String> groupNames,
                                       String facilityCapabilitiesAttrName,
                                       String resourceCapabilitiesAttrName) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        if (facility == null) {
            return new HashSet<>();
        }

        Set<String> capabilities = new HashSet<>();
        Set<String> resourceGroupNames = new HashSet<>();

        if (null != resourceCapabilitiesAttrName) {
            List<Resource> resources = getAssignedRichResources(facility);
            for (Resource resource : resources) {
                PerunAttributeValue attrValue = getResourceAttributeValue(resource.getId(), resourceCapabilitiesAttrName);

                List<String> resourceCapabilities = attrValue.valueAsList();
                if (resourceCapabilities == null || resourceCapabilities.isEmpty()) {
                    continue;
                }
                List<Group> groups = getAssignedGroups(resource.getId());
                for (Group group : groups) {
                    resourceGroupNames.add(group.getName());
                    String groupName = group.getName();
                    if (resource.getVo() != null) {
                        groupName = resource.getVo().getShortName() + ':' + groupName;
                    }
                    group.setUniqueGroupName(groupName);

                    if (groupNames.contains(groupName)) {
                        log.trace("Group [{}] found in users groups, add capabilities [{}]", groupName, resourceCapabilities);
                        capabilities.addAll(resourceCapabilities);
                    } else {
                        log.trace("Group [{}] not found in users groups, continue to the next one", groupName);
                    }
                }
            }
        }

        if (null != facilityCapabilitiesAttrName && !Collections.disjoint(groupNames, resourceGroupNames)) {
            Set<String> facilityCapabilities = getFacilityCapabilities(facility, facilityCapabilitiesAttrName);
            capabilities.addAll(facilityCapabilities);
        }

        return capabilities;
    }

    @Override
    public Set<String> getCapabilities(Facility facility, Map<Long, String> idToGnameMap,
                                       String facilityCapabilitiesAttrName, String resourceCapabilitiesAttrName) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        if (facility == null) {
            return new HashSet<>();
        }

        return getCapabilities(facility, new HashSet<>(idToGnameMap.values()), facilityCapabilitiesAttrName,
                resourceCapabilitiesAttrName);
    }

    @Override
    public Set<Group> getGroupsWhereUserIsActiveWithUniqueNames(Long facilityId,
                                                                Long userId,
                                                                String resourceGroupEntitlementDisabledAttribute) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        Set<Group> groups = getGroupsWhereUserIsActive(facilityId, userId, resourceGroupEntitlementDisabledAttribute);

        Map<Long, String> voIdToShortNameMap = new HashMap<>();
        groups.forEach(g -> {
            if (!voIdToShortNameMap.containsKey(g.getVoId())) {
                Vo vo = getVoById(g.getVoId());
                if (vo != null) {
                    voIdToShortNameMap.put(vo.getId(), vo.getShortName());
                }
            }
            g.setUniqueGroupName(voIdToShortNameMap.get(g.getVoId()) + ':' + g.getName());
        });

        return groups;
    }

    @Override
    public Set<Long> getUserGroupsIds(Long userId, Long voId) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        Member member = getMemberByUser(userId, voId);
        Set<Long> groups = new HashSet<>();
        if (member != null) {
            groups = getMemberGroups(member.getId()).stream().map(Group::getId).collect(Collectors.toSet());
        }

        return groups;
    }

    @Override
    public Set<String> getVosAndGroupsNamesWhereUserInvalid(Long userId, Set<Long> vos, Set<Long> groups) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }
        if (vos == null) {
            vos = new HashSet<>();
        }
        if (groups == null) {
            groups = new HashSet<>();
        }
        List<Member> members = getMembersByUser(userId);
        Set<Long> foundVoIds = new HashSet<>();
        Set<Long> foundGroupIds = new HashSet<>();
        boolean skipGroups = groups.isEmpty();

        for (Member m : members) {
            if (MemberStatus.VALID.equals(m.getStatus())) {
                foundVoIds.add(m.getVoId());
            }
            if (!skipGroups) {
                foundGroupIds.addAll(getMemberGroups(m.getId()).stream().map(Model::getId).collect(Collectors.toList()));
            }
        }

        Set<Long> voIdsToRegister = new HashSet<>(vos);
        voIdsToRegister.removeAll(foundVoIds);

        Set<Long> groupIdsToRegister = new HashSet<>(groups);
        groupIdsToRegister.removeAll(foundGroupIds);

        Set<String> unitNames = new HashSet<>();

        final Map<Long, Vo> voCache = new HashMap<>();
        voIdsToRegister.forEach(voId -> {
            Vo vo = getVoById(voId);
            if (vo != null) {
                voCache.put(vo.getId(), vo);
                unitNames.add(vo.getShortName());
            }
        });


        groupIdsToRegister.forEach(groupId -> {
            Group group = getGroupById(groupId);
            if (group != null) {
                Vo vo = voCache.getOrDefault(group.getVoId(), null);
                if (vo == null) {
                    vo = getVoById(group.getVoId());
                    if (vo == null) {
                        return;
                    }
                    voCache.put(group.getVoId(), vo);
                }
                unitNames.add((vo.getShortName() + ":" + group.getName()));
            }
        });
        return unitNames;
    }

    @Override
    public boolean isUserInVo(Long userId, String voShortName) {
        if (!connectorRpc.isEnabled()) {
            return false;
        }
        if (userId == null) {
            throw new IllegalArgumentException("No userId");
        } else if (!StringUtils.hasText(voShortName)) {
            throw new IllegalArgumentException("No voShortName");
        }

        Vo vo = getVoByShortName(voShortName);
        if (vo == null || vo.getId() == null) {
            log.debug("isUserInVo - No VO found, returning false");
            return false;
        }
        try {
            Member member = getMemberByUser(userId, vo.getId());
            if (member == null) {
                log.debug("isUserInVo - No member found, returning false");
                return false;
            }
            return VALID.equals(member.getStatus());
        } catch (Exception e) {
            log.debug("isUserInVo - caught exception, probably user is not a member");
            log.trace("{}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public boolean hasApplicationForm(String voShortName) {
        if (!connectorRpc.isEnabled()) {
            return false;
        }

        Vo vo = getVoByShortName(voShortName);
        if (vo == null || vo.getId() == null) {
            return false;
        }
        return hasApplicationForm(vo.getId());
    }

    @Override
    public PerunUser getPerunUser(Long userId) {
        if (!connectorRpc.isEnabled()) {
            return null;
        } else if (userId == null) {
            throw new IllegalArgumentException("No userId");
        }
        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_ID, userId);

        JsonNode response = connectorRpc.post(USERS_MANAGER, METHOD_GET_USER_BY_ID, map);
        return RpcMapper.mapPerunUser(response);
    }

    @Override
    public Set<Long> getUserVoIds(Long userId) {
        if (!connectorRpc.isEnabled()) {
            return Collections.emptySet();
        } else if (userId == null) {
            throw new IllegalArgumentException("No userId");
        }
        List<Member> members = getMembersByUser(userId);
        Set<Long> voIds = new HashSet<>();
        for (Member member : members) {
            if (VALID == member.getStatus()) {
                voIds.add(member.getVoId());
            }
        }
        return voIds;
    }

    @Override
    public Map<Vo, Set<Group>> getRegistrationUnits(Set<String> invalidUnits) {
        if (!connectorRpc.isEnabled()) {
            return Collections.emptyMap();
        }
        Map<String, Set<String>> voShortNamesToGroupNames = new HashMap<>();
        for (String invalidUnit : invalidUnits) {
            String[] parts = invalidUnit.split(":", 2);
            String voShortName = parts[0];
            voShortNamesToGroupNames.putIfAbsent(voShortName, new HashSet<>());
            if (parts.length > 1 && !PerunConstants.GROUP_NAME_MEMBERS.equals(parts[1])) {
                voShortNamesToGroupNames.get(voShortName).add(parts[1]);
            }
        }

        Map<Vo, Set<Group>> res = new HashMap<>();
        for (Map.Entry<String, Set<String>> e : voShortNamesToGroupNames.entrySet()) {
            Vo vo = getVoByShortName(e.getKey());
            if (vo != null && hasApplicationForm(vo.getId())) {
                res.put(vo, new HashSet<>());
                for (String groupName : e.getValue()) {
                    Group group = getGroupByName(vo, groupName);
                    if (group != null && hasApplicationForm(vo.getId())) {
                        res.get(vo).add(group);
                    }
                }
            }
        }
        return res;
    }

    private Group getGroupByName(Vo vo, String name) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        Map<String, Object> params = new LinkedHashMap<>();
        params.put(PARAM_VO, vo.getId());
        params.put(PARAM_NAME, name);
        JsonNode jsonNode = connectorRpc.post(GROUPS_MANAGER, METHOD_GET_GROUP_BY_NAME, params);

        return RpcMapper.mapGroup(jsonNode);
    }

    private Member getMemberByUser(Long userId, Long voId) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        Map<String, Object> params = new LinkedHashMap<>();
        params.put(PARAM_USER, userId);
        params.put(PARAM_VO, voId);
        JsonNode jsonNode = connectorRpc.post(MEMBERS_MANAGER, METHOD_GET_MEMBER_BY_USER, params);

        return RpcMapper.mapMember(jsonNode);
    }

    private Set<Group> getGroupsWhereUserIsActive(Long facilityId, Long userId, String ignoreGroupsAttribute) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        Set<Group> groups = new HashSet<>();
        if (StringUtils.hasText(ignoreGroupsAttribute)) {
            Set<Resource> resources = getResourcesAssignedToFacility(facilityId, userId, ignoreGroupsAttribute);
            for (Resource resource : resources) {
                groups.addAll(
                        getGroupsWhereUserIsActiveByResource(resource.getId(), userId)
                );
            }
        } else {
            groups.addAll(getGroupsWhereUserIsActiveByFacility(facilityId, userId));
        }
        return groups;
    }

    private Set<Group> getGroupsWhereUserIsActiveByFacility(Long facilityId, Long userId) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_FACILITY, facilityId);
        map.put(PARAM_USER, userId);
        JsonNode jsonNode = connectorRpc.post(USERS_MANAGER, METHOD_GET_GROUPS_WHERE_USER_IS_ACTIVE, map);

        return new HashSet<>(RpcMapper.mapGroups(jsonNode));
    }

    private Set<Resource> getResourcesAssignedToFacility(Long facilityId, Long userId, String ignoreAttribute) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }
        Set<Resource> resources = getAllowedResources(facilityId, userId);
        Set<Resource> result = new HashSet<>();
        for (Resource resource : resources) {
            PerunAttributeValue attrValue = getResourceAttributeValue(resource.getId(), ignoreAttribute);
            if (attrValue == null || attrValue.isNullValue() || !attrValue.valueAsBoolean()) {
                result.add(resource);
            } else {
                log.debug(
                        "getResourcesAssignedToFacility - non null attr value for '{}', skipping resource '{}'",
                        ignoreAttribute, resource
                );
            }
        }
        return result;
    }

    private Set<Group> getGroupsWhereUserIsActiveByResource(Long resourceId, Long userId) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_USER, userId);
        map.put(PARAM_RESOURCE, resourceId);

        JsonNode res = connectorRpc.post(USERS_MANAGER, METHOD_GET_GROUPS_WHERE_USER_IS_ACTIVE, map);
        return new HashSet<>(RpcMapper.mapGroups(res));
    }

    private Set<Resource> getAllowedResources(Long facilityId, Long userId) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_USER, userId);
        map.put(PARAM_FACILITY, facilityId);

        JsonNode res = connectorRpc.post(USERS_MANAGER, METHOD_GET_ALLOWED_RESOURCES, map);
        return new HashSet<>(RpcMapper.mapResources(res));
    }

    private Vo getVoById(Long voId) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_ID, voId);

        JsonNode res = connectorRpc.post(VOS_MANAGER, METHOD_GET_VO_BY_ID, map);
        return RpcMapper.mapVo(res);
    }

    private Group getGroupById(Long groupId) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_ID, groupId);

        JsonNode res = connectorRpc.post(GROUPS_MANAGER, METHOD_GET_GROUP_BY_ID, map);
        return RpcMapper.mapGroup(res);
    }

    private List<Group> getAssignedGroups(Long resourceId) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> params = new LinkedHashMap<>();
        params.put(PARAM_RESOURCE, resourceId);

        JsonNode response = connectorRpc.post(RESOURCES_MANAGER, METHOD_GET_ASSIGNED_GROUPS, params);

        return RpcMapper.mapGroups(response);
    }

    private Map<String, PerunAttributeValue> extractValues(Map<String, PerunAttribute> attributeMap) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, PerunAttributeValue> resultMap = new LinkedHashMap<>();
        for (Map.Entry<String, PerunAttribute> attrPair : attributeMap.entrySet()) {
            String attrName = attrPair.getKey();
            PerunAttribute attr = attrPair.getValue();
            if (attr != null) {
                resultMap.put(attrName, attr.toPerunAttributeValue());
            }
        }

        return resultMap;
    }

    private Map<String, PerunAttribute> getAttributes(PerunEntityType entity, Long entityId, Collection<String> attrsToFetch) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        } else if (attrsToFetch == null || attrsToFetch.isEmpty()) {
            return new HashMap<>();
        }

        Set<AttributeMapping> mappings;
        switch (entity) {
            case USER:
                mappings = getUserAttributesMappingService()
                        .getMappingsByIdentifiers(attrsToFetch);
                break;
            case FACILITY:
                mappings = getFacilityAttributesMappingService()
                        .getMappingsByIdentifiers(attrsToFetch);
                break;
            case VO:
                mappings = getVoAttributesMappingService()
                        .getMappingsByIdentifiers(attrsToFetch);
                break;
            case GROUP:
                mappings = getGroupAttributesMappingService()
                        .getMappingsByIdentifiers(attrsToFetch);
                break;
            case RESOURCE:
                mappings = getResourceAttributesMappingService()
                        .getMappingsByIdentifiers(attrsToFetch);
                break;
            default:
                mappings = new HashSet<>();
                break;
        }

        List<String> rpcNames = mappings.stream().map(AttributeMapping::getRpcName).collect(Collectors.toList());

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(entity.toString().toLowerCase(), entityId);
        map.put(PARAM_ATTR_NAMES, rpcNames);

        JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, METHOD_GET_ATTRIBUTES, map);
        return RpcMapper.mapAttributes(res, mappings);
    }

    private List<Group> getMemberGroups(Long memberId) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_MEMBER, memberId);

        JsonNode response = connectorRpc.post(GROUPS_MANAGER, METHOD_GET_MEMBER_GROUPS, map);
        return RpcMapper.mapGroups(response);
    }

    private List<Member> getMembersByUser(Long userId) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> params = new LinkedHashMap<>();
        params.put(PARAM_USER, userId);
        JsonNode jsonNode = connectorRpc.post(MEMBERS_MANAGER, METHOD_GET_MEMBERS_BY_USER, params);

        return RpcMapper.mapMembers(jsonNode);
    }

    private List<Vo> getVosByShortNames(List<String> voShortNames) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        List<Vo> vos = new ArrayList<>();
        for (String shortName : voShortNames) {
            Vo vo = getVoByShortName(shortName);
            vos.add(vo);
        }

        return vos;
    }

    private Map<Long, MemberStatus> convertMembersListToStatusesMap(List<Member> userMembers) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<Long, MemberStatus> res = new HashMap<>();
        for (Member m : userMembers) {
            res.put(m.getVoId(), m.getStatus());
        }

        return res;
    }

    private Map<String, PerunAttributeValue> getUserExtSourceAttributeValues(Long uesId, Set<AttributeMapping> attrMappings) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_USER_EXT_SOURCE, uesId);
        map.put(PARAM_ATTR_NAMES, attrMappings.stream().map(AttributeMapping::getRpcName).collect(Collectors.toList()));

        JsonNode response = connectorRpc.post(ATTRIBUTES_MANAGER, METHOD_GET_ATTRIBUTES, map);
        Map<String, PerunAttribute> attributeMap = RpcMapper.mapAttributes(response, attrMappings);
        return extractValues(attributeMap);
    }

    private List<Group> getAllowedGroups(Facility facility) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_FACILITY, facility.getId());
        JsonNode jsonNode = connectorRpc.post(FACILITIES_MANAGER, METHOD_GET_ALLOWED_GROUPS, map);
        List<Group> result = new ArrayList<>();
        for (int i = 0; i < jsonNode.size(); i++) {
            JsonNode groupNode = jsonNode.get(i);
            result.add(RpcMapper.mapGroup(groupNode));
        }

        return result;
    }

    private boolean hasApplicationForm(Group group) {
        if (!connectorRpc.isEnabled()) {
            return false;
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_GROUP, group.getId());
        try {
            if (PerunConstants.GROUP_NAME_MEMBERS.equals(group.getName())) {
                log.debug("hasApplicationForm({}) continues to call regForm for VO {}", group, group.getVoId());
                return hasApplicationForm(group.getVoId());
            } else {
                connectorRpc.post(REGISTRAR_MANAGER, METHOD_GET_APPLICATION_FORM, map);
            }
        } catch (Exception e) {
            // when group does not have form exception is thrown. Every error thus is supposed as group without form
            // this method will be used after calling other RPC methods - if RPC is not available other methods should discover it first
            return false;
        }

        return true;
    }

    private boolean hasApplicationForm(Long voId) {
        if (!connectorRpc.isEnabled()) {
            return false;
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_VO, voId);
        try {
            connectorRpc.post(REGISTRAR_MANAGER, METHOD_GET_APPLICATION_FORM, map);
        } catch (Exception e) {
            // when vo does not have form exception is thrown. Every error thus is supposed as vo without form
            // this method will be used after calling other RPC methods - if RPC is not available other methods should discover it first
            return false;
        }

        return true;
    }

    private Map<Long, Vo> convertVoListToMap(List<Vo> vos) {
        if (!connectorRpc.isEnabled()) {
            return new HashMap<>();
        }

        Map<Long, Vo> map = new HashMap<>();
        for (Vo vo : vos) {
            map.put(vo.getId(), vo);
        }

        return map;
    }

    private List<Resource> getAssignedResources(Facility facility) {
        return getAssignedResources(facility.getId());
    }

    private List<Resource> getAssignedResources(Long facilityId) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_FACILITY, facilityId);

        JsonNode res = connectorRpc.post(FACILITIES_MANAGER, METHOD_GET_ASSIGNED_RESOURCES, map);
        return RpcMapper.mapResources(res);
    }

    private List<Resource> getAssignedRichResources(Facility facility) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_FACILITY, facility.getId());

        JsonNode res = connectorRpc.post(FACILITIES_MANAGER, METHOD_GET_ASSIGNED_RICH_RESOURCES, map);
        return RpcMapper.mapResources(res);
    }

    private List<Group> getRichGroupsAssignedToResourceWithAttributesByNames(Resource resource, List<String> attrNames) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        Set<AttributeMapping> mappings = getGroupAttributesMappingService()
                .getMappingsByIdentifiers(attrNames);
        List<String> rpcNames = mappings.stream().map(AttributeMapping::getRpcName).collect(Collectors.toList());
        map.put(PARAM_RESOURCE, resource.getId());
        map.put(PARAM_ATTR_NAMES, rpcNames);

        JsonNode res = connectorRpc.post(GROUPS_MANAGER, METHOD_GET_RICH_GROUPS_ASSIGNED_TO_RESOURCE_WITH_ATTRIBUTES_BY_NAMES, map);
        List<Group> groups = new ArrayList<>();

        for (int i = 0; i < res.size(); i++) {
            JsonNode jsonNode = res.get(i);
            Group group = RpcMapper.mapGroup(jsonNode);

            JsonNode groupAttrs = jsonNode.get(FIELD_ATTRIBUTES);
            Map<String, JsonNode> attrsMap = new HashMap<>();

            for (int j = 0; j < groupAttrs.size(); j++) {
                JsonNode attr = groupAttrs.get(j);

                String namespace = attr.get(FIELD_NAMESPACE).textValue();
                String friendlyName = attr.get(FIELD_FRIENDLY_NAME).textValue();

                attrsMap.put(namespace + ":" + friendlyName, attr);
            }

            group.setAttributes(attrsMap);
            groups.add(group);
        }

        return groups;
    }

    private PerunAttribute getAttribute(PerunEntityType entity, Long entityId, String attributeName) {
        if (!connectorRpc.isEnabled()) {
            return null;
        }

        AttributeMapping mapping;
        switch (entity) {
            case USER:
                mapping = getUserAttributesMappingService()
                        .getMappingByIdentifier(attributeName);
                break;
            case FACILITY:
                mapping = getFacilityAttributesMappingService()
                        .getMappingByIdentifier(attributeName);
                break;
            case VO:
                mapping = getVoAttributesMappingService()
                        .getMappingByIdentifier(attributeName);
                break;
            case GROUP:
                mapping = getGroupAttributesMappingService()
                        .getMappingByIdentifier(attributeName);
                break;
            case RESOURCE:
                mapping = getResourceAttributesMappingService()
                        .getMappingByIdentifier(attributeName);
                break;
            default:
                throw new IllegalArgumentException("Unrecognized entity");
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(entity.toString().toLowerCase(), entityId);
        map.put(PARAM_ATTRIBUTE_NAME, mapping.getRpcName());

        JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, METHOD_GET_ATTRIBUTE, map);
        return RpcMapper.mapAttribute(res);
    }

    private List<UserExtSource> getUserExtSources(Long userId) {
        if (!connectorRpc.isEnabled()) {
            return new ArrayList<>();
        }

        Map<String, Object> map = new LinkedHashMap<>();
        map.put(PARAM_USER, userId);

        JsonNode response = connectorRpc.post(USERS_MANAGER, METHOD_GET_USER_EXT_SOURCES, map);
        return RpcMapper.mapUserExtSources(response);
    }

    private Set<String> getFacilityCapabilities(Facility facility, String capabilitiesAttrName) {
        if (!connectorRpc.isEnabled()) {
            return new HashSet<>();
        }

        Set<String> capabilities = new HashSet<>();
        if (facility != null) {
            PerunAttributeValue attr = getFacilityAttributeValue(facility, capabilitiesAttrName);
            if (attr != null && attr.valueAsList() != null) {
                capabilities = new HashSet<>(attr.valueAsList());
            }
        }

        return capabilities;
    }

}
