#!/usr/bin/python

# Copyright 2012 Dag Wieers <dag@wieers.com>
# 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 annotations

DOCUMENTATION = r"""
module: hpilo_info
author: Dag Wieers (@dagwieers)
short_description: Gather information through an HP iLO interface
description:
  - This module gathers information on a specific system using its HP iLO interface. These information includes hardware and
    network related information useful for provisioning (for example macaddress, uuid).
  - This module requires the C(hpilo) python module.
extends_documentation_fragment:
  - community.general._attributes
  - community.general._attributes.info_module
options:
  host:
    description:
      - The HP iLO hostname/address that is linked to the physical system.
    type: str
    required: true
  login:
    description:
      - The login name to authenticate to the HP iLO interface.
    type: str
    default: Administrator
  password:
    description:
      - The password to authenticate to the HP iLO interface.
    type: str
    default: admin
  ssl_version:
    description:
      - Change the ssl_version used.
    default: TLSv1
    type: str
    choices: ["SSLv3", "SSLv23", "TLSv1", "TLSv1_1", "TLSv1_2"]
requirements:
  - hpilo
notes:
  - This module ought to be run from a system that can access the HP iLO interface directly, either by using C(local_action)
    or using C(delegate_to).
"""

EXAMPLES = r"""
- name: Gather facts from a HP iLO interface only if the system is an HP server
  community.general.hpilo_info:
    host: YOUR_ILO_ADDRESS
    login: YOUR_ILO_LOGIN
    password: YOUR_ILO_PASSWORD
  when: cmdb_hwmodel.startswith('HP ')
  delegate_to: localhost
  register: results

- ansible.builtin.fail:
    msg: 'CMDB serial ({{ cmdb_serialno }}) does not match hardware serial ({{ results.hw_system_serial }}) !'
  when: cmdb_serialno != results.hw_system_serial
"""

RETURN = r"""
# Typical output of HP iLO_info for a physical system
hw_bios_date:
  description: BIOS date.
  returned: always
  type: str
  sample: 05/05/2011

hw_bios_version:
  description: BIOS version.
  returned: always
  type: str
  sample: P68

hw_ethX:
  description: Interface information (for each interface).
  returned: always
  type: dict
  sample:
    - macaddress: 00:11:22:33:44:55
      macaddress_dash: 00-11-22-33-44-55

hw_eth_ilo:
  description: Interface information (for the iLO network interface).
  returned: always
  type: dict
  sample:
    - macaddress: 00:11:22:33:44:BA
    - macaddress_dash: 00-11-22-33-44-BA

hw_product_name:
  description: Product name.
  returned: always
  type: str
  sample: ProLiant DL360 G7

hw_product_uuid:
  description: Product UUID.
  returned: always
  type: str
  sample: ef50bac8-2845-40ff-81d9-675315501dac

hw_system_serial:
  description: System serial number.
  returned: always
  type: str
  sample: ABC12345D6

hw_uuid:
  description: Hardware UUID.
  returned: always
  type: str
  sample: 123456ABC78901D2

host_power_status:
  description:
    - Power status of host.
    - It is one of V(ON), V(OFF) and V(UNKNOWN).
  returned: always
  type: str
  sample: "ON"
  version_added: 3.5.0
"""

import re
import traceback
import warnings

HPILO_IMP_ERR = None
try:
    import hpilo

    HAS_HPILO = True
except ImportError:
    HPILO_IMP_ERR = traceback.format_exc()
    HAS_HPILO = False

from ansible.module_utils.basic import AnsibleModule, missing_required_lib

# Suppress warnings from hpilo
warnings.simplefilter("ignore")


def parse_flat_interface(entry, non_numeric="hw_eth_ilo"):
    try:
        infoname = f"hw_eth{int(entry['Port']) - 1}"
    except Exception:
        infoname = non_numeric

    info = {"macaddress": entry["MAC"].replace("-", ":"), "macaddress_dash": entry["MAC"]}
    return (infoname, info)


def main():
    module = AnsibleModule(
        argument_spec=dict(
            host=dict(type="str", required=True),
            login=dict(type="str", default="Administrator"),
            password=dict(type="str", default="admin", no_log=True),
            ssl_version=dict(type="str", default="TLSv1", choices=["SSLv3", "SSLv23", "TLSv1", "TLSv1_1", "TLSv1_2"]),
        ),
        supports_check_mode=True,
    )

    if not HAS_HPILO:
        module.fail_json(msg=missing_required_lib("python-hpilo"), exception=HPILO_IMP_ERR)

    host = module.params["host"]
    login = module.params["login"]
    password = module.params["password"]
    ssl_version = getattr(hpilo.ssl, f"PROTOCOL_{module.params.get('ssl_version').upper().replace('V', 'v')}")

    ilo = hpilo.Ilo(host, login=login, password=password, ssl_version=ssl_version)

    info = {
        "module_hw": True,
    }

    # TODO: Count number of CPUs, DIMMs and total memory
    try:
        data = ilo.get_host_data()
        power_state = ilo.get_host_power_status()
    except hpilo.IloCommunicationError as e:
        module.fail_json(msg=f"{e}")

    for entry in data:
        if "type" not in entry:
            continue
        elif entry["type"] == 0:  # BIOS Information
            info["hw_bios_version"] = entry["Family"]
            info["hw_bios_date"] = entry["Date"]
        elif entry["type"] == 1:  # System Information
            info["hw_uuid"] = entry["UUID"]
            info["hw_system_serial"] = entry["Serial Number"].rstrip()
            info["hw_product_name"] = entry["Product Name"]
            info["hw_product_uuid"] = entry["cUUID"]
        elif entry["type"] == 209:  # Embedded NIC MAC Assignment
            if "fields" in entry:
                for name, value in [(e["name"], e["value"]) for e in entry["fields"]]:
                    if name.startswith("Port"):
                        try:
                            infoname = f"hw_eth{int(value) - 1}"
                        except Exception:
                            infoname = "hw_eth_ilo"
                    elif name.startswith("MAC"):
                        info[infoname] = {"macaddress": value.replace("-", ":"), "macaddress_dash": value}
            else:
                (infoname, entry_info) = parse_flat_interface(entry, "hw_eth_ilo")
                info[infoname] = entry_info
        elif entry["type"] == 209:  # HPQ NIC iSCSI MAC Info
            for name, value in [(e["name"], e["value"]) for e in entry["fields"]]:
                if name.startswith("Port"):
                    try:
                        infoname = f"hw_iscsi{int(value) - 1}"
                    except Exception:
                        infoname = "hw_iscsi_ilo"
                elif name.startswith("MAC"):
                    info[infoname] = {"macaddress": value.replace("-", ":"), "macaddress_dash": value}
        elif entry["type"] == 233:  # Embedded NIC MAC Assignment (Alternate data format)
            (infoname, entry_info) = parse_flat_interface(entry, "hw_eth_ilo")
            info[infoname] = entry_info

    # Collect health (RAM/CPU data)
    health = ilo.get_embedded_health()
    info["hw_health"] = health

    memory_details_summary = health.get("memory", {}).get("memory_details_summary")
    # RAM as reported by iLO 2.10 on ProLiant BL460c Gen8
    if memory_details_summary:
        info["hw_memory_details_summary"] = memory_details_summary
        info["hw_memory_total"] = 0
        for details in memory_details_summary.values():
            cpu_total_memory_size = details.get("total_memory_size")
            if cpu_total_memory_size:
                ram = re.search(r"(\d+)\s+(\w+)", cpu_total_memory_size)
                if ram:
                    if ram.group(2) == "GB":
                        info["hw_memory_total"] = info["hw_memory_total"] + int(ram.group(1))

        # reformat into a text friendly format
        info["hw_memory_total"] = f"{info['hw_memory_total']} GB"

    # Report host state
    info["host_power_status"] = power_state or "UNKNOWN"

    module.exit_json(**info)


if __name__ == "__main__":
    main()
