#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2017, 2018 Kairo Araujo <kairo@kairo.eti.br>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = r'''
---
author:
- Kairo Araujo (@kairoaraujo)
module: aix_devices
short_description: Manages AIX devices
description:
- This module discovers, defines, removes and modifies attributes of AIX devices.
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  attributes:
    description:
    - A list of device attributes.
    type: dict
  device:
    description:
    - The name of the device.
    - V(all) is valid to rescan C(available) all devices (AIX cfgmgr command).
    type: str
  force:
    description:
    - Forces action.
    type: bool
    default: false
  recursive:
    description:
    - Removes or defines a device and children devices.
    type: bool
    default: false
  state:
    description:
    - Controls the device state.
    - V(available) (alias V(present)) rescan a specific device or all devices (when O(device) is not specified).
    - V(removed) (alias V(absent) removes a device.
    - V(defined) changes device to Defined state.
    type: str
    choices: [ available, defined, removed ]
    default: available
'''

EXAMPLES = r'''
- name: Scan new devices
  community.general.aix_devices:
    device: all
    state: available

- name: Scan new virtual devices (vio0)
  community.general.aix_devices:
    device: vio0
    state: available

- name: Removing IP alias to en0
  community.general.aix_devices:
    device: en0
    attributes:
      delalias4: 10.0.0.100,255.255.255.0

- name: Removes ent2
  community.general.aix_devices:
    device: ent2
    state: removed

- name: Put device en2 in Defined
  community.general.aix_devices:
    device: en2
    state: defined

- name: Removes ent4 (inexistent).
  community.general.aix_devices:
    device: ent4
    state: removed

- name: Put device en4 in Defined (inexistent)
  community.general.aix_devices:
    device: en4
    state: defined

- name: Put vscsi1 and children devices in Defined state.
  community.general.aix_devices:
    device: vscsi1
    recursive: true
    state: defined

- name: Removes vscsi1 and children devices.
  community.general.aix_devices:
    device: vscsi1
    recursive: true
    state: removed

- name: Changes en1 mtu to 9000 and disables arp.
  community.general.aix_devices:
    device: en1
    attributes:
      mtu: 900
      arp: 'off'
    state: available

- name: Configure IP, netmask and set en1 up.
  community.general.aix_devices:
    device: en1
    attributes:
      netaddr: 192.168.0.100
      netmask: 255.255.255.0
      state: up
    state: available

- name: Adding IP alias to en0
  community.general.aix_devices:
    device: en0
    attributes:
      alias4: 10.0.0.100,255.255.255.0
    state: available
'''

RETURN = r''' # '''

from ansible.module_utils.basic import AnsibleModule


def _check_device(module, device):
    """
    Check if device already exists and the state.
    Args:
        module: Ansible module.
        device: device to be checked.

    Returns: bool, device state

    """
    lsdev_cmd = module.get_bin_path('lsdev', True)
    rc, lsdev_out, err = module.run_command(["%s" % lsdev_cmd, '-C', '-l', "%s" % device])

    if rc != 0:
        module.fail_json(msg="Failed to run lsdev", rc=rc, err=err)

    if lsdev_out:
        device_state = lsdev_out.split()[1]
        return True, device_state

    device_state = None
    return False, device_state


def _check_device_attr(module, device, attr):
    """

    Args:
        module: Ansible module.
        device: device to check attributes.
        attr: attribute to be checked.

    Returns:

    """
    lsattr_cmd = module.get_bin_path('lsattr', True)
    rc, lsattr_out, err = module.run_command(["%s" % lsattr_cmd, '-El', "%s" % device, '-a', "%s" % attr])

    hidden_attrs = ['delalias4', 'delalias6']

    if rc == 255:

        if attr in hidden_attrs:
            current_param = ''
        else:
            current_param = None

        return current_param

    elif rc != 0:
        module.fail_json(msg="Failed to run lsattr: %s" % err, rc=rc, err=err)

    current_param = lsattr_out.split()[1]
    return current_param


def discover_device(module, device):
    """ Discover AIX devices."""
    cfgmgr_cmd = module.get_bin_path('cfgmgr', True)

    if device is not None:
        device = "-l %s" % device

    else:
        device = ''

    changed = True
    msg = ''
    if not module.check_mode:
        rc, cfgmgr_out, err = module.run_command(["%s" % cfgmgr_cmd, "%s" % device])
        changed = True
        msg = cfgmgr_out

    return changed, msg


def change_device_attr(module, attributes, device, force):
    """ Change AIX device attribute. """

    attr_changed = []
    attr_not_changed = []
    attr_invalid = []
    chdev_cmd = module.get_bin_path('chdev', True)

    for attr in list(attributes.keys()):
        new_param = attributes[attr]
        current_param = _check_device_attr(module, device, attr)

        if current_param is None:
            attr_invalid.append(attr)

        elif current_param != new_param:
            if force:
                cmd = ["%s" % chdev_cmd, '-l', "%s" % device, '-a', "%s=%s" % (attr, attributes[attr]), "%s" % force]
            else:
                cmd = ["%s" % chdev_cmd, '-l', "%s" % device, '-a', "%s=%s" % (attr, attributes[attr])]

            if not module.check_mode:
                rc, chdev_out, err = module.run_command(cmd)
                if rc != 0:
                    module.exit_json(msg="Failed to run chdev.", rc=rc, err=err)

            attr_changed.append(attributes[attr])
        else:
            attr_not_changed.append(attributes[attr])

    if len(attr_changed) > 0:
        changed = True
        attr_changed_msg = "Attributes changed: %s. " % ','.join(attr_changed)
    else:
        changed = False
        attr_changed_msg = ''

    if len(attr_not_changed) > 0:
        attr_not_changed_msg = "Attributes already set: %s. " % ','.join(attr_not_changed)
    else:
        attr_not_changed_msg = ''

    if len(attr_invalid) > 0:
        attr_invalid_msg = "Invalid attributes: %s " % ', '.join(attr_invalid)
    else:
        attr_invalid_msg = ''

    msg = "%s%s%s" % (attr_changed_msg, attr_not_changed_msg, attr_invalid_msg)

    return changed, msg


def remove_device(module, device, force, recursive, state):
    """ Puts device in defined state or removes device. """

    state_opt = {
        'removed': '-d',
        'absent': '-d',
        'defined': ''
    }

    recursive_opt = {
        True: '-R',
        False: ''
    }

    recursive = recursive_opt[recursive]
    state = state_opt[state]

    changed = True
    msg = ''
    rmdev_cmd = module.get_bin_path('rmdev', True)

    if not module.check_mode:
        if state:
            rc, rmdev_out, err = module.run_command(["%s" % rmdev_cmd, "-l", "%s" % device, "%s" % recursive, "%s" % force])
        else:
            rc, rmdev_out, err = module.run_command(["%s" % rmdev_cmd, "-l", "%s" % device, "%s" % recursive])

        if rc != 0:
            module.fail_json(msg="Failed to run rmdev", rc=rc, err=err)

        msg = rmdev_out

    return changed, msg


def main():

    module = AnsibleModule(
        argument_spec=dict(
            attributes=dict(type='dict'),
            device=dict(type='str'),
            force=dict(type='bool', default=False),
            recursive=dict(type='bool', default=False),
            state=dict(type='str', default='available', choices=['available', 'defined', 'removed']),
        ),
        supports_check_mode=True,
    )

    force_opt = {
        True: '-f',
        False: '',
    }

    attributes = module.params['attributes']
    device = module.params['device']
    force = force_opt[module.params['force']]
    recursive = module.params['recursive']
    state = module.params['state']

    result = dict(
        changed=False,
        msg='',
    )

    if state == 'available' or state == 'present':
        if attributes:
            # change attributes on device
            device_status, device_state = _check_device(module, device)
            if device_status:
                result['changed'], result['msg'] = change_device_attr(module, attributes, device, force)
            else:
                result['msg'] = "Device %s does not exist." % device

        else:
            # discovery devices (cfgmgr)
            if device and device != 'all':
                device_status, device_state = _check_device(module, device)
                if device_status:
                    # run cfgmgr on specific device
                    result['changed'], result['msg'] = discover_device(module, device)

                else:
                    result['msg'] = "Device %s does not exist." % device

            else:
                result['changed'], result['msg'] = discover_device(module, device)

    elif state == 'removed' or state == 'absent' or state == 'defined':
        if not device:
            result['msg'] = "device is required to removed or defined state."

        else:
            # Remove device
            check_device, device_state = _check_device(module, device)
            if check_device:
                if state == 'defined' and device_state == 'Defined':
                    result['changed'] = False
                    result['msg'] = 'Device %s already in Defined' % device

                else:
                    result['changed'], result['msg'] = remove_device(module, device, force, recursive, state)

            else:
                result['msg'] = "Device %s does not exist." % device

    else:
        result['msg'] = "Unexpected state %s." % state
        module.fail_json(**result)

    module.exit_json(**result)


if __name__ == '__main__':
    main()