#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright Ansible Project
# 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 = '''
---
module: ipmi_power
short_description: Power management for machine
description:
  - Use this module for power management
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  name:
    description:
      - Hostname or ip address of the BMC.
    required: true
    type: str
  port:
    description:
      - Remote RMCP port.
    default: 623
    type: int
  user:
    description:
      - Username to use to connect to the BMC.
    required: true
    type: str
  password:
    description:
      - Password to connect to the BMC.
    required: true
    type: str
  key:
    description:
      - Encryption key to connect to the BMC in hex format.
    required: false
    type: str
    version_added: 4.1.0
  state:
    description:
      - Whether to ensure that the machine in desired state.
      - "The choices for state are:
            - on -- Request system turn on
            - off -- Request system turn off without waiting for OS to shutdown
            - shutdown -- Have system request OS proper shutdown
            - reset -- Request system reset without waiting for OS
            - boot -- If system is off, then 'on', else 'reset'"
      - Either this option or O(machine) is required.
    choices: ['on', 'off', shutdown, reset, boot]
    type: str
  timeout:
    description:
      - Maximum number of seconds before interrupt request.
    default: 300
    type: int
  machine:
    description:
      - Provide a list of the remote target address for the bridge IPMI request,
        and the power status.
      - Either this option or O(state) is required.
    required: false
    type: list
    elements: dict
    version_added: 4.3.0
    suboptions:
      targetAddress:
        description:
          - Remote target address for the bridge IPMI request.
        type: int
        required: true
      state:
        description:
          - Whether to ensure that the machine specified by O(machine[].targetAddress) in desired state.
          - If this option is not set, the power state is set by O(state).
          - If both this option and O(state) are set, this option takes precedence over O(state).
        choices: ['on', 'off', shutdown, reset, boot]
        type: str

requirements:
  - pyghmi
author: "Bulat Gaifullin (@bgaifullin) <gaifullinbf@gmail.com>"
'''

RETURN = '''
powerstate:
    description: The current power state of the machine.
    returned: success and O(machine) is not provided
    type: str
    sample: 'on'
status:
    description: The current power state of the machine when the machine option is set.
    returned: success and O(machine) is provided
    type: list
    elements: dict
    version_added: 4.3.0
    contains:
        powerstate:
          description: The current power state of the machine specified by RV(status[].targetAddress).
          type: str
        targetAddress:
          description: The remote target address.
          type: int
    sample: [
              {
                "powerstate": "on",
                "targetAddress": 48,
              },
              {
                "powerstate": "on",
                "targetAddress": 50,
              },
    ]
'''

EXAMPLES = '''
- name: Ensure machine is powered on
  community.general.ipmi_power:
    name: test.testdomain.com
    user: admin
    password: password
    state: 'on'

- name: Ensure machines of which remote target address is 48 and 50 are powered off
  community.general.ipmi_power:
    name: test.testdomain.com
    user: admin
    password: password
    state: 'off'
    machine:
      - targetAddress: 48
      - targetAddress: 50

- name: Ensure machine of which remote target address is 48 is powered on, and 50 is powered off
  community.general.ipmi_power:
    name: test.testdomain.com
    user: admin
    password: password
    machine:
      - targetAddress: 48
        state: 'on'
      - targetAddress: 50
        state: 'off'
'''

import traceback
import binascii

PYGHMI_IMP_ERR = None
INVALID_TARGET_ADDRESS = 0x100
try:
    from pyghmi.ipmi import command
except ImportError:
    PYGHMI_IMP_ERR = traceback.format_exc()
    command = None

from ansible.module_utils.basic import AnsibleModule, missing_required_lib


def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(required=True),
            port=dict(default=623, type='int'),
            state=dict(choices=['on', 'off', 'shutdown', 'reset', 'boot']),
            user=dict(required=True, no_log=True),
            password=dict(required=True, no_log=True),
            key=dict(type='str', no_log=True),
            timeout=dict(default=300, type='int'),
            machine=dict(
                type='list', elements='dict',
                options=dict(
                    targetAddress=dict(required=True, type='int'),
                    state=dict(type='str', choices=['on', 'off', 'shutdown', 'reset', 'boot']),
                ),
            ),
        ),
        supports_check_mode=True,
        required_one_of=(
            ['state', 'machine'],
        ),
    )

    if command is None:
        module.fail_json(msg=missing_required_lib('pyghmi'), exception=PYGHMI_IMP_ERR)

    name = module.params['name']
    port = module.params['port']
    user = module.params['user']
    password = module.params['password']
    state = module.params['state']
    timeout = module.params['timeout']
    machine = module.params['machine']

    try:
        if module.params['key']:
            key = binascii.unhexlify(module.params['key'])
        else:
            key = None
    except Exception:
        module.fail_json(msg="Unable to convert 'key' from hex string.")

    # --- run command ---
    try:
        ipmi_cmd = command.Command(
            bmc=name, userid=user, password=password, port=port, kg=key
        )
        module.debug('ipmi instantiated - name: "%s"' % name)

        changed = False
        if machine is None:
            current = ipmi_cmd.get_power()
            if current['powerstate'] != state:
                response = {'powerstate': state} if module.check_mode \
                    else ipmi_cmd.set_power(state, wait=timeout)
                changed = True
            else:
                response = current

            if 'error' in response:
                module.fail_json(msg=response['error'])

            module.exit_json(changed=changed, **response)
        else:
            response = []
            for entry in machine:
                taddr = entry['targetAddress']
                if taddr >= INVALID_TARGET_ADDRESS:
                    module.fail_json(msg="targetAddress should be set between 0 to 255.")

                try:
                    # bridge_request is supported on pyghmi 1.5.30 and later
                    current = ipmi_cmd.get_power(bridge_request={"addr": taddr})
                except TypeError:
                    module.fail_json(
                        msg="targetAddress isn't supported on the installed pyghmi.")

                if entry['state']:
                    tstate = entry['state']
                elif state:
                    tstate = state
                else:
                    module.fail_json(msg="Either state or suboption of machine state should be set.")

                if current['powerstate'] != tstate:
                    changed = True
                    if not module.check_mode:
                        new = ipmi_cmd.set_power(tstate, wait=timeout, bridge_request={"addr": taddr})
                        if 'error' in new:
                            module.fail_json(msg=new['error'])

                        response.append(
                            {'targetAddress:': taddr, 'powerstate': new['powerstate']})

                if current['powerstate'] == tstate or module.check_mode:
                    response.append({'targetAddress:': taddr, 'powerstate': tstate})

            module.exit_json(changed=changed, status=response)
    except Exception as e:
        module.fail_json(msg=str(e))


if __name__ == '__main__':
    main()
