#!/usr/bin/python
#
# Copyright (c) 2020 Paul Aiton, (@paultaiton)
# Copyright (c) 2018 Yunge Zhu, (@yungezz)
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = '''
---
module: azure_rm_roleassignment
version_added: "0.1.2"
short_description: Manage Azure Role Assignment
description:
    - Create and delete instance of Azure Role Assignment.

options:
    assignee_object_id:
        description:
            - The object id of assignee. This maps to the ID inside the Active Directory.
            - It can point to a user, service principal or security group.
            - Required when creating role assignment.
        type: str
        aliases:
          - assignee
    id:
        description:
            - Fully qualified id of assignment to delete or create.
            - Mutually Exclusive with I(scope) and I(name)
        type: str
    name:
        description:
            - Unique name of role assignment.
            - The role assignment name must be a GUID, sample as "3ce0cbb0-58c4-4e6d-a16d-99d86a78b3ca".
            - Mutually Exclusive with I(id)
        type: str
    role_definition_id:
        description:
            - The role definition id used in the role assignment.
            - Required when creating role assignment.
        type: str
    scope:
        description:
            - The scope of the role assignment to create.
            - For example, use /subscriptions/{subscription-id}/ for subscription.
            - /subscriptions/{subscription-id}/resourceGroups/{resource-group-name} for resource group.
            - /subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/{resource-provider}/{resource-type}/{resource-name} for resource.
            - Mutually Exclusive with I(id)
        type: str
    state:
        description:
            - Assert the state of the role assignment.
            - Use C(present) to create or update a role assignment and C(absent) to delete it.
            - If C(present), then I(role_definition_id) and I(assignee_object_id) are both required
        default: present
        type: str
        choices:
            - absent
            - present

extends_documentation_fragment:
    - azure.azcollection.azure

author:
    - Yunge Zhu(@yungezz)
    - Paul Aiton(@paultaiton)

'''

EXAMPLES = '''
- name: Create a role assignment
  azure_rm_roleassignment:
    scope: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    assignee_object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    role_definition_id:
      "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

- name: Create a role assignment
  azure_rm_roleassignment:
    name: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    scope: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    assignee_object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    role_definition_id:
      "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

- name: Delete a role assignment
  azure_rm_roleassignment:
    name: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    scope: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    state: absent

- name: Delete a role assignment
  azure_rm_roleassignment:
    id: /subscriptions/xxx-sub-guid-xxx/resourceGroups/rgname/providers/Microsoft.Authorization/roleAssignments/xxx-assign-guid-xxx"
    state: absent

- name: Delete a role assignment
  azure_rm_roleassignment:
    scope: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    assignee_object_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    role_definition_id:
      "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    state: absent
'''

RETURN = '''
id:
    description:
        - Id of current role assignment.
    returned: always
    type: str
    sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleAssignments/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name:
    description:
        - Name of role assignment.
    type: str
    returned: always
    sample: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
type:
    description:
        - Type of role assignment.
    type: str
    returned: always
    sample: Microsoft.Authorization/roleAssignments
assignee_object_id:
    description:
        - Principal Id of the role assignee.
    type: str
    returned: always
    sample: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
principal_type:
    description:
        - Principal type of the role assigned to.
    type: str
    returned: always
    sample: ServicePrincipal
role_definition_id:
    description:
        - Role definition id that was assigned to principal_id.
    type: str
    returned: always
    sample: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
scope:
    description:
        - The role assignment scope.
    type: str
    returned: always
    sample: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
'''

try:
    import uuid
    from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase

except ImportError:
    # This is handled in azure_rm_common
    pass


class AzureRMRoleAssignment(AzureRMModuleBase):
    """Configuration class for an Azure RM Role Assignment"""

    def __init__(self):
        self.module_arg_spec = dict(
            assignee_object_id=dict(type='str', aliases=['assignee']),
            id=dict(type='str'),
            name=dict(type='str'),
            role_definition_id=dict(type='str'),
            scope=dict(type='str'),
            state=dict(type='str', default='present', choices=['present', 'absent'])
        )

        self.assignee_object_id = None
        self.id = None
        self.name = None
        self.role_definition_id = None
        self.scope = None
        self.state = None

        self.results = dict(
            changed=False,
            id=None,
        )

        mutually_exclusive = [['name', 'id'], ['scope', 'id']]
        required_one_of = [['scope', 'id']]
        required_if = [
            ["state", "present", ["assignee_object_id", "role_definition_id"]]
        ]

        super(AzureRMRoleAssignment, self).__init__(derived_arg_spec=self.module_arg_spec,
                                                    supports_check_mode=True,
                                                    supports_tags=False,
                                                    required_one_of=required_one_of,
                                                    required_if=required_if,
                                                    mutually_exclusive=mutually_exclusive)

    def exec_module(self, **kwargs):
        """Main module execution method"""

        for key in self.module_arg_spec:
            setattr(self, key, kwargs[key])

        if self.name and not self.scope:
            self.fail("Parameter Error: setting name requires a scope to also be set.")

        existing_assignment = None
        response = None

        existing_assignment = self.get_roleassignment()

        if existing_assignment:
            self.set_results(existing_assignment)

        if self.state == 'present':
            # check if the role assignment exists
            if not existing_assignment:
                self.log("Role assignment doesn't exist in this scope")

                self.results['changed'] = True

                if self.check_mode:
                    return self.results
                response = self.create_roleassignment()
                self.set_results(response)

            else:
                self.log("Role assignment already exists, not updatable")
                self.log('Result: {0}'.format(existing_assignment))

        elif self.state == 'absent':
            if existing_assignment:
                self.log("Delete role assignment")
                self.results['changed'] = True

                if self.check_mode:
                    return self.results

                self.delete_roleassignment(existing_assignment.get('id'))

                self.log('role assignment deleted')

            else:
                # If assignment doesn't exist, that's the desired state.
                self.log("role assignment {0} does not exist.".format(self.name))

        return self.results

    def create_roleassignment(self):
        '''
        Creates role assignment.

        :return: deserialized role assignment
        '''
        self.log("Creating role assignment {0}".format(self.name))

        response = None
        try:
            # pylint: disable=missing-kwoa
            parameters = self.authorization_models.RoleAssignmentCreateParameters(role_definition_id=self.role_definition_id,
                                                                                  principal_id=self.assignee_object_id)
            if self.id:
                response = self.authorization_client.role_assignments.create_by_id(role_id=self.id,
                                                                                   parameters=parameters)
            elif self.scope:
                if not self.name:
                    self.name = str(uuid.uuid4())
                response = self.authorization_client.role_assignments.create(scope=self.scope,
                                                                             role_assignment_name=self.name,
                                                                             parameters=parameters)

        except Exception as exc:
            self.log('Error attempting to create role assignment.')
            self.fail("Error creating role assignment: {0}".format(str(exc)))
        return self.roleassignment_to_dict(response)

    def delete_roleassignment(self, assignment_id):
        '''
        Deletes specified role assignment.

        :return: True
        '''
        self.log("Deleting the role assignment {0}".format(self.name))
        try:
            response = self.authorization_client.role_assignments.delete_by_id(role_id=assignment_id)
        except Exception as e:
            self.log('Error attempting to delete the role assignment.')
            self.fail("Error deleting the role assignment: {0}".format(str(e)))

        return True

    def get_roleassignment(self):
        '''
        Gets the properties of the specified role assignment.

        :return: deserialized role assignment dictionary
        '''
        self.log("Checking if the role assignment {0} is present".format(self.name))

        role_assignment = None

        if self.id:
            try:
                response = self.authorization_client.role_assignments.get_by_id(role_id=self.id)
                role_assignment = self.roleassignment_to_dict(response)
                if role_assignment and self.assignee_object_id and role_assignment.get('assignee_object_id') != self.assignee_object_id:
                    self.fail('State Mismatch Error: The assignment ID exists, but does not match the provided assignee.')

                if role_assignment and self.role_definition_id and (role_assignment.get('role_definition_id').split('/')[-1].lower()
                                                                    != self.role_definition_id.split('/')[-1].lower()):
                    self.fail('State Mismatch Error: The assignment ID exists, but does not match the provided role.')

            except Exception as ex:
                self.log("Didn't find role assignments id {0}".format(self.id))

        elif self.name and self.scope:
            try:
                response = self.authorization_client.role_assignments.get(scope=self.scope, role_assignment_name=self.name)
                role_assignment = self.roleassignment_to_dict(response)
                if role_assignment and self.assignee_object_id and role_assignment.get('assignee_object_id') != self.assignee_object_id:
                    self.fail('State Mismatch Error: The assignment name exists, but does not match the provided assignee.')

                if role_assignment and self.role_definition_id and (role_assignment.get('role_definition_id').split('/')[-1].lower()
                                                                    != self.role_definition_id.split('/')[-1].lower()):
                    self.fail('State Mismatch Error: The assignment name exists, but does not match the provided role.')

            except Exception as ex:
                self.log("Didn't find role assignment by name {0} at scope {1}".format(self.name, self.scope))

        else:
            try:
                if self.scope and self.assignee_object_id and self.role_definition_id:
                    response = list(self.authorization_client.role_assignments.list())
                    response = [self.roleassignment_to_dict(role_assignment) for role_assignment in response]
                    response = [role_assignment for role_assignment in response if role_assignment.get('scope').lower() == self.scope.rstrip("/").lower()]
                    response = [role_assignment for role_assignment in response if (role_assignment.get('assignee_object_id').lower()
                                                                                    == self.assignee_object_id.lower())]
                    response = [role_assignment for role_assignment in response if (role_assignment.get('role_definition_id').split('/')[-1].lower()
                                                                                    == self.role_definition_id.split('/')[-1].lower())]
                else:
                    self.fail('If id or name are not supplied, then assignee_object_id and role_definition_id are required.')
                if response:
                    role_assignment = response[0]
            except Exception as ex:
                self.log("Didn't find role assignments for subscription {0}".format(self.subscription_id))

        return role_assignment

    def set_results(self, assignment):
        self.results['id'] = assignment.get('id')
        self.results['name'] = assignment.get('name')
        self.results['type'] = assignment.get('type')
        self.results['assignee_object_id'] = assignment.get('assignee_object_id')
        self.results['principal_type'] = assignment.get('principal_type')
        self.results['role_definition_id'] = assignment.get('role_definition_id')
        self.results['scope'] = assignment.get('scope')

    def roleassignment_to_dict(self, assignment):
        return dict(
            assignee_object_id=assignment.principal_id,
            id=assignment.id,
            name=assignment.name,
            principal_id=assignment.principal_id,
            principal_type=assignment.principal_type,
            role_definition_id=assignment.role_definition_id,
            scope=assignment.scope,
            type=assignment.type
        )


def main():
    """Main execution"""
    AzureRMRoleAssignment()


if __name__ == '__main__':
    main()
