#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from heat.common import exception
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine import support


class KeystoneRoleAssignmentMixin(object):
    """Implements role assignments between user/groups and project/domain.

    heat_template_version: 2013-05-23

    parameters:
      ... Group or User parameters
      group_role:
        type: string
        description: role
      group_role_domain:
        type: string
        description: group role domain
      group_role_project:
        type: string
        description: group role project

    resources:
      admin_group:
        type: OS::Keystone::Group OR OS::Keystone::User
        properties:
          ... Group or User properties
          roles:
            - role: {get_param: group_role}
              domain: {get_param: group_role_domain}
            - role: {get_param: group_role}
              project: {get_param: group_role_project}
    """

    PROPERTIES = (
        ROLES
    ) = (
        'roles'
    )

    _ROLES_MAPPING_PROPERTIES = (
        ROLE, DOMAIN, PROJECT
    ) = (
        'role', 'domain', 'project'
    )

    mixin_properties_schema = {
        ROLES: properties.Schema(
            properties.Schema.LIST,
            _('List of role assignments.'),
            schema=properties.Schema(
                properties.Schema.MAP,
                _('Map between role with either project or domain.'),
                schema={
                    ROLE: properties.Schema(
                        properties.Schema.STRING,
                        _('Keystone role.'),
                        required=True,
                        constraints=([constraints.
                                     CustomConstraint('keystone.role')])
                    ),
                    PROJECT: properties.Schema(
                        properties.Schema.STRING,
                        _('Keystone project.'),
                        constraints=([constraints.
                                     CustomConstraint('keystone.project')])
                    ),
                    DOMAIN: properties.Schema(
                        properties.Schema.STRING,
                        _('Keystone domain.'),
                        constraints=([constraints.
                                     CustomConstraint('keystone.domain')])
                    ),
                }
            ),
            update_allowed=True
        )
    }

    def _add_role_assignments_to_group(self, group_id, role_assignments):
        for role_assignment in self._normalize_to_id(role_assignments):
            if role_assignment.get(self.PROJECT) is not None:
                self.client().roles.grant(
                    role=role_assignment.get(self.ROLE),
                    project=role_assignment.get(self.PROJECT),
                    group=group_id
                )
            elif role_assignment.get(self.DOMAIN) is not None:
                self.client().roles.grant(
                    role=role_assignment.get(self.ROLE),
                    domain=role_assignment.get(self.DOMAIN),
                    group=group_id
                )

    def _add_role_assignments_to_user(self, user_id, role_assignments):
        for role_assignment in self._normalize_to_id(role_assignments):
            if role_assignment.get(self.PROJECT) is not None:
                self.client().roles.grant(
                    role=role_assignment.get(self.ROLE),
                    project=role_assignment.get(self.PROJECT),
                    user=user_id
                )
            elif role_assignment.get(self.DOMAIN) is not None:
                self.client().roles.grant(
                    role=role_assignment.get(self.ROLE),
                    domain=role_assignment.get(self.DOMAIN),
                    user=user_id
                )

    def _remove_role_assignments_from_group(self, group_id, role_assignments):
        for role_assignment in self._normalize_to_id(role_assignments):
            if role_assignment.get(self.PROJECT) is not None:
                self.client().roles.revoke(
                    role=role_assignment.get(self.ROLE),
                    project=role_assignment.get(self.PROJECT),
                    group=group_id
                )
            elif role_assignment.get(self.DOMAIN) is not None:
                self.client().roles.revoke(
                    role=role_assignment.get(self.ROLE),
                    domain=role_assignment.get(self.DOMAIN),
                    group=group_id
                )

    def _remove_role_assignments_from_user(self, user_id, role_assignments):
        for role_assignment in self._normalize_to_id(role_assignments):
            if role_assignment.get(self.PROJECT) is not None:
                self.client().roles.revoke(
                    role=role_assignment.get(self.ROLE),
                    project=role_assignment.get(self.PROJECT),
                    user=user_id
                )
            elif role_assignment.get(self.DOMAIN) is not None:
                self.client().roles.revoke(
                    role=role_assignment.get(self.ROLE),
                    domain=role_assignment.get(self.DOMAIN),
                    user=user_id
                )

    def _normalize_to_id(self, role_assignment_prps):
        role_assignments = []
        if role_assignment_prps is None:
            return role_assignments

        for role_assignment in role_assignment_prps:
            role = role_assignment.get(self.ROLE)
            project = role_assignment.get(self.PROJECT)
            domain = role_assignment.get(self.DOMAIN)

            role_assignments.append({
                self.ROLE: self.client_plugin().get_role_id(role),
                self.PROJECT: (self.client_plugin().
                               get_project_id(project)) if project else None,
                self.DOMAIN: (self.client_plugin().
                              get_domain_id(domain)) if domain else None
            })
        return role_assignments

    def _find_differences(self, updated_prps, stored_prps):
        updated_role_project_assignments = []
        updated_role_domain_assignments = []

        # Split the properties into two set of role assignments
        # (project, domain) from updated properties
        for role_assignment in updated_prps or []:
            if role_assignment.get(self.PROJECT) is not None:
                updated_role_project_assignments.append(
                    '%s:%s' % (
                        role_assignment[self.ROLE],
                        role_assignment[self.PROJECT]))
            elif (role_assignment.get(self.DOMAIN)
                  is not None):
                updated_role_domain_assignments.append(
                    '%s:%s' % (role_assignment[self.ROLE],
                               role_assignment[self.DOMAIN]))

        stored_role_project_assignments = []
        stored_role_domain_assignments = []

        # Split the properties into two set of role assignments
        # (project, domain) from updated properties
        for role_assignment in (stored_prps or []):
            if role_assignment.get(self.PROJECT) is not None:
                stored_role_project_assignments.append(
                    '%s:%s' % (
                        role_assignment[self.ROLE],
                        role_assignment[self.PROJECT]))
            elif (role_assignment.get(self.DOMAIN)
                  is not None):
                stored_role_domain_assignments.append(
                    '%s:%s' % (role_assignment[self.ROLE],
                               role_assignment[self.DOMAIN]))

        new_role_assignments = []
        removed_role_assignments = []
        # NOTE: finding the diff of list of strings is easier by using 'set'
        #       so properties are converted to string in above sections
        # New items
        for item in (set(updated_role_project_assignments) -
                     set(stored_role_project_assignments)):
            new_role_assignments.append(
                {self.ROLE: item[:item.find(':')],
                 self.PROJECT: item[item.find(':') + 1:]}
            )

        for item in (set(updated_role_domain_assignments) -
                     set(stored_role_domain_assignments)):
            new_role_assignments.append(
                {self.ROLE: item[:item.find(':')],
                 self.DOMAIN: item[item.find(':') + 1:]}
            )