#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017, David Passante (@dpassante)
# 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: cs_role_permission
short_description: Manages role permissions on Apache CloudStack based clouds.
description:
    - Create, update and remove CloudStack role permissions.
    - Managing role permissions only supported in CloudStack >= 4.9.
author: David Passante (@dpassante)
version_added: 0.1.0
options:
  name:
    description:
      - The API name of the permission.
    type: str
    required: true
  role:
    description:
      - Name or ID of the role.
    type: str
    required: true
  permission:
    description:
      - The rule permission, allow or deny. Defaulted to deny.
    type: str
    choices: [ allow, deny ]
    default: deny
  state:
    description:
      - State of the role permission.
    type: str
    choices: [ present, absent ]
    default: present
  description:
    description:
      - The description of the role permission.
    type: str
  parent:
    description:
      - The parent role permission uuid. use 0 to move this rule at the top of the list.
    type: str
extends_documentation_fragment:
- ngine_io.cloudstack.cloudstack
'''

EXAMPLES = '''
- name: Create a role permission
  ngine_io.cloudstack.cs_role_permission:
    role: My_Custom_role
    name: createVPC
    permission: allow
    description: My comments

- name: Remove a role permission
  ngine_io.cloudstack.cs_role_permission:
    state: absent
    role: My_Custom_role
    name: createVPC

- name: Update a system role permission
  ngine_io.cloudstack.cs_role_permission:
    role: Domain Admin
    name: createVPC
    permission: deny

- name: Update rules order. Move the rule at the top of list
  ngine_io.cloudstack.cs_role_permission:
    role: Domain Admin
    name: createVPC
    parent: 0
'''

RETURN = '''
---
id:
  description: The ID of the role permission.
  returned: success
  type: str
  sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
name:
  description: The API name of the permission.
  returned: success
  type: str
  sample: createVPC
permission:
  description: The permission type of the api name.
  returned: success
  type: str
  sample: allow
role_id:
  description: The ID of the role to which the role permission belongs.
  returned: success
  type: str
  sample: c6f7a5fc-43f8-11e5-a151-feff819cdc7f
description:
  description: The description of the role permission
  returned: success
  type: str
  sample: Deny createVPC for users
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import raise_from

try:
    from ansible.module_utils.compat.version import LooseVersion
except ImportError:
    try:
        from distutils.version import LooseVersion
    except ImportError as exc:
        msg = 'To use this plugin or module with ansible-core 2.11, you need to use Python < 3.12 with distutils.version present'
        raise_from(ImportError(msg), exc)

from ..module_utils.cloudstack import (AnsibleCloudStack, cs_argument_spec,
                                       cs_required_together)


class AnsibleCloudStackRolePermission(AnsibleCloudStack):

    def __init__(self, module):
        super(AnsibleCloudStackRolePermission, self).__init__(module)
        cloudstack_min_version = LooseVersion('4.9.2')

        self.returns = {
            'id': 'id',
            'roleid': 'role_id',
            'rule': 'name',
            'permission': 'permission',
            'description': 'description',
        }
        self.role_permission = None

        self.cloudstack_version = self._cloudstack_ver()

        if self.cloudstack_version < cloudstack_min_version:
            self.fail_json(msg="This module requires CloudStack >= %s." % cloudstack_min_version)

    def _cloudstack_ver(self):
        capabilities = self.get_capabilities()
        return LooseVersion(capabilities['cloudstackversion'])

    def _get_role_id(self):
        role = self.module.params.get('role')
        if not role:
            return None

        res = self.query_api('listRoles')
        roles = res['role']
        if roles:
            for r in roles:
                if role in [r['name'], r['id']]:
                    return r['id']
        self.fail_json(msg="Role '%s' not found" % role)

    def _get_role_perm(self):
        role_permission = self.role_permission

        args = {
            'roleid': self._get_role_id(),
        }

        rp = self.query_api('listRolePermissions', **args)

        if rp:
            role_permission = rp['rolepermission']

        return role_permission

    def _get_rule(self, rule=None):
        if not rule:
            rule = self.module.params.get('name')

        if self._get_role_perm():
            for _rule in self._get_role_perm():
                if rule == _rule['rule'] or rule == _rule['id']:
                    return _rule

        return None

    def _get_rule_order(self):
        perms = self._get_role_perm()
        rules = []

        if perms:
            for i, rule in enumerate(perms):
                rules.append(rule['id'])

        return rules

    def replace_rule(self):
        old_rule = self._get_rule()

        if old_rule:
            rules_order = self._get_rule_order()
            old_pos = rules_order.index(old_rule['id'])

            self.remove_role_perm()

            new_rule = self.create_role_perm()

            if new_rule:
                perm_order = self.order_permissions(int(old_pos - 1), new_rule['id'])

                return perm_order

        return None

    def order_permissions(self, parent, rule_id):
        rules = self._get_rule_order()

        if isinstance(parent, int):
            parent_pos = parent
        elif parent == '0':
            parent_pos = -1
        else:
            parent_rule = self._get_rule(parent)
            if not parent_rule:
                self.fail_json(msg="Parent rule '%s' not found" % parent)

            parent_pos = rules.index(parent_rule['id'])

        r_id = rules.pop(rules.index(rule_id))

        rules.insert((parent_pos + 1), r_id)
        rules = ','.join(map(str, rules))

        return rules

    def create_or_update_role_perm(self):
        role_permission = self._get_rule()

        if not role_permission:
            role_permission = self.create_role_perm()
        else:
            role_permission = self.update_role_perm(role_permission)

        return role_permission

    def create_role_perm(self):
        role_permission = None

        self.result['changed'] = True

        args = {
            'rule': self.module.params.get('name'),
            'description': self.module.params.get('description'),
            'roleid': self._get_role_id(),
            'permission': self.module.params.get('permission'),
        }

        if not self.module.check_mode:
            res = self.query_api('createRolePermission', **args)
            role_permission = res['rolepermission']

        return role_permission

    def update_role_perm(self, role_perm):
        perm_order = None

        if not self.module.params.get('parent'):
            args = {
                'ruleid': role_perm['id'],
                'roleid': role_perm['roleid'],
                'permission': self.module.params.get('permission'),
            }

            if self.has_changed(args, role_perm, only_keys=['permission']):
                self.result['changed'] = True

                if not self.module.check_mode:
                    if self.cloudstack_version >= LooseVersion('4.11.0'):
                        self.query_api('updateRolePermission', **args)
                        role_perm = self._get_rule()
                    else:
                        perm_order = self.replace_rule()
        else:
            perm_order = self.order_permissions(self.module.params.get('parent'), role_perm['id'])

        if perm_order:
            args = {
                'roleid': role_perm['roleid'],
                'ruleorder': perm_order,
            }

            self.result['changed'] = True

            if not self.module.check_mode:
                self.query_api('updateRolePermission', **args)
                role_perm = self._get_rule()

        return role_perm

    def remove_role_perm(self):
        role_permission = self._get_rule()

        if role_permission:
            self.result['changed'] = True

            args = {
                'id': role_permission['id'],
            }

            if not self.module.check_mode:
                self.query_api('deleteRolePermission', **args)

        return role_permission


def main():
    argument_spec = cs_argument_spec()
    argument_spec.update(dict(
        role=dict(required=True),
        name=dict(required=True),
        permission=dict(choices=['allow', 'deny'], default='deny'),
        description=dict(),
        state=dict(choices=['present', 'absent'], default='present'),
        parent=dict(),
    ))

    module = AnsibleModule(
        argument_spec=argument_spec,
        required_together=cs_required_together(),
        mutually_exclusive=(
            ['permission', 'parent'],
        ),
        supports_check_mode=True
    )

    acs_role_perm = AnsibleCloudStackRolePermission(module)

    state = module.params.get('state')
    if state in ['absent']:
        role_permission = acs_role_perm.remove_role_perm()
    else:
        role_permission = acs_role_perm.create_or_update_role_perm()

    result = acs_role_perm.get_result(role_permission)
    module.exit_json(**result)


if __name__ == '__main__':
    main()
