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

#
# Dell OpenManage Ansible Modules
# Version 8.1.0
# Copyright (C) 2021-2023 Dell Inc. or its subsidiaries. All Rights Reserved.

# 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: ome_device_local_access_configuration
short_description: Configure local access settings on OpenManage Enterprise Modular.
description: This module allows to configure the local access settings of the power button, quick sync, KVM,
  LCD, and chassis direct access on OpenManage Enterprise Modular.
version_added: "4.4.0"
extends_documentation_fragment:
 - dellemc.openmanage.omem_auth_options
options:
  device_id:
    type: int
    description:
      - The ID of the chassis for which the local access configuration to be updated.
      - If the device ID is not specified, this module updates the local access settings for the I(hostname).
      - I(device_id) is mutually exclusive with I(device_service_tag).
  device_service_tag:
    type: str
    description:
      - The service tag of the chassis for which the local access settings needs to be updated.
      - If the device service tag is not specified, this module updates the local access settings for the I(hostname).
      - I(device_service_tag) is mutually exclusive with I(device_id).
  enable_kvm_access:
    type: bool
    description: Enables or disables the keyboard, video, and mouse (KVM) interfaces.
  enable_chassis_direct_access:
    type: bool
    description: Enables or disables the access to management consoles such as iDRAC and the management module of
      the device on the chassis.
  chassis_power_button:
    type: dict
    description: The settings for the chassis power button.
    suboptions:
      enable_chassis_power_button:
        required: true
        type: bool
        description:
          - Enables or disables the chassis power button.
          - If C(false), the chassis cannot be turn on or turn off using the power button.
      enable_lcd_override_pin:
        type: bool
        description:
          - Enables or disables the LCD override pin.
          - This is required when I(enable_chassis_power_button) is C(false).
      disabled_button_lcd_override_pin:
        type: str
        description:
          - The six digit LCD override pin to change the power state of the chassis.
          - This is required when I(enable_lcd_override_pin) is C(true).
          - The module will always report change when I(disabled_button_lcd_override_pin) is C(true).
          - 'The value must be specified in quotes. ex: "001100".'
  quick_sync:
    type: dict
    description:
      - The settings for quick sync.
      - The I(quick_sync) options are ignored if the quick sync hardware is not present.
    suboptions:
      quick_sync_access:
        type: str
        choices: [READ_WRITE, READ_ONLY, DISABLED]
        description:
          - Users with administrator privileges can set the following types of I(quick_sync_access).
          - C(READ_WRITE) enables writing configuration using quick sync.
          - C(READ_ONLY) enables read only access to Wi-Fi and Bluetooth Low Energy(BLE).
          - C(DISABLED) disables reading or writing configuration through quick sync.
      enable_inactivity_timeout:
        type: bool
        description: Enables or disables the inactivity timeout.
      timeout_limit:
        type: int
        description:
          - Inactivity timeout in seconds or minutes.
          - The range is 120 to 3600 in seconds, or 2 to 60 in minutes.
          - This option is required when I(enable_inactivity_timeout) is C(true).
      timeout_limit_unit:
        type: str
        choices: [SECONDS, MINUTES]
        description:
          - Inactivity timeout limit unit.
          - C(SECONDS) to set I(timeout_limit) in seconds.
          - C(MINUTES) to set I(timeout_limit) in minutes.
          - This option is required when I(enable_inactivity_timeout) is C(true).
      enable_read_authentication:
        type: bool
        description: Enables or disables the option to log in using your user credentials and to read the
          inventory in a secure data center.
      enable_quick_sync_wifi:
        type: bool
        description: Enables or disables the Wi-Fi communication path to the chassis.
  lcd:
    type: dict
    description:
      - The settings for LCD.
      - The I(lcd) options are ignored if the LCD hardware is not present in the chassis.
    suboptions:
      lcd_access:
        type: str
        choices: [VIEW_AND_MODIFY, VIEW_ONLY, DISABLED]
        description:
          - Option to configure the quick sync settings using LCD.
          - C(VIEW_AND_MODIFY) to set access level to view and modify.
          - C(VIEW_ONLY) to set access level to view.
          - C(DISABLED) to disable the access.
      user_defined:
        type: str
        description: The text to display on the LCD Home screen. The LCD Home screen is displayed when the system
          is reset to factory default settings. The user-defined text can have a maximum of 62 characters.
      lcd_language:
        type: str
        description:
          - The language code in which the text on the LCD must be displayed.
          - en to set English language.
          - fr to set French language.
          - de to set German language.
          - es to set Spanish language.
          - ja to set Japanese language.
          - zh to set Chinese language.
requirements:
  - "python >= 3.8.6"
author:
  - "Felix Stephen (@felixs88)"
  - "Shivam Sharma (@ShivamSh3)"
notes:
  - Run this module from a system that has direct access to OpenManage Enterprise Modular.
  - This module supports C(check_mode).
  - The module will always report change when I(enable_chassis_power_button) is C(true).
"""

EXAMPLES = """
---
- name: Configure KVM, direct access and power button settings of the chassis using device ID.
  dellemc.openmanage.ome_device_local_access_configuration:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    ca_path: "/path/to/ca_cert.pem"
    device_id: 25011
    enable_kvm_access: true
    enable_chassis_direct_access: false
    chassis_power_button:
      enable_chassis_power_button: false
      enable_lcd_override_pin: true
      disabled_button_lcd_override_pin: "123456"

- name: Configure Quick sync and LCD settings of the chassis using device service tag.
  dellemc.openmanage.ome_device_local_access_configuration:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    ca_path: "/path/to/ca_cert.pem"
    device_service_tag: GHRT2RL
    quick_sync:
      quick_sync_access: READ_ONLY
      enable_read_authentication: true
      enable_quick_sync_wifi: true
      enable_inactivity_timeout: true
      timeout_limit: 10
      timeout_limit_unit: MINUTES
    lcd:
      lcd_access: VIEW_ONLY
      lcd_language: en
      user_defined: "LCD Text"

- name: Configure all local access settings of the host chassis.
  dellemc.openmanage.ome_device_local_access_configuration:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    ca_path: "/path/to/ca_cert.pem"
    enable_kvm_access: true
    enable_chassis_direct_access: false
    chassis_power_button:
      enable_chassis_power_button: false
      enable_lcd_override_pin: true
      disabled_button_lcd_override_pin: "123456"
    quick_sync:
      quick_sync_access: READ_WRITE
      enable_read_authentication: true
      enable_quick_sync_wifi: true
      enable_inactivity_timeout: true
      timeout_limit: 120
      timeout_limit_unit: SECONDS
    lcd:
      lcd_access: VIEW_MODIFY
      lcd_language: en
      user_defined: "LCD Text"
"""

RETURN = """
---
msg:
  type: str
  description: Overall status of the device local access settings.
  returned: always
  sample: "Successfully updated the local access settings."
location_details:
  type: dict
  description: returned when local access settings are updated successfully.
  returned: success
  sample: {
    "SettingType": "LocalAccessConfiguration",
    "EnableChassisDirect": false,
    "EnableChassisPowerButton": false,
    "EnableKvmAccess": true,
    "EnableLcdOverridePin": false,
    "LcdAccess": "VIEW_ONLY",
    "LcdCustomString": "LCD Text",
    "LcdLanguage": "en",
    "LcdOverridePin": "",
    "LcdPinLength": null,
    "LcdPresence": "Present",
    "LedPresence": null,
    "QuickSync": {
      "EnableInactivityTimeout": true,
      "EnableQuickSyncWifi": false,
      "EnableReadAuthentication": false,
      "QuickSyncAccess": "READ_ONLY",
      "QuickSyncHardware": "Present",
      "TimeoutLimit": 7,
      "TimeoutLimitUnit": "MINUTES"
      }
    }
error_info:
  description: Details of the HTTP Error.
  returned: on HTTP error
  type: dict
  sample: {
    "error": {
      "code": "Base.1.0.GeneralError",
      "message": "A general error has occurred. See ExtendedInfo for more information.",
      "@Message.ExtendedInfo": [
        {
          "MessageId": "GEN1234",
          "RelatedProperties": [],
          "Message": "Unable to process the request because an error occurred.",
          "MessageArgs": [],
          "Severity": "Critical",
          "Resolution": "Retry the operation. If the issue persists, contact your system administrator."
        }
      ]
    }
  }
"""


import json
import socket
import copy
from ssl import SSLError
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
from ansible.module_utils.urls import ConnectionError
from ansible_collections.dellemc.openmanage.plugins.module_utils.ome import RestOME, ome_auth_params

DOMAIN_URI = "ManagementDomainService/Domains"
DEVICE_URI = "DeviceService/Devices"
LAC_API = "DeviceService/Devices({0})/Settings('LocalAccessConfiguration')"
CONFIG_FAIL_MSG = "one of the following is required: enable_kvm_access, enable_chassis_direct_access, " \
                  "chassis_power_button, quick_sync, lcd"
DOMAIN_FAIL_MSG = "The operation to configure the local access is supported only on " \
                  "OpenManage Enterprise Modular."
FETCH_FAIL_MSG = "Unable to retrieve the device information."
DEVICE_FAIL_MSG = "Unable to complete the operation because the entered target device {0} '{1}' is invalid."
LAC_FAIL_MSG = "Unable to complete the operation because the local access configuration settings " \
               "are not supported on the specified device."
CHANGES_FOUND = "Changes found to be applied."
NO_CHANGES_FOUND = "No changes found to be applied."
SUCCESS_MSG = "Successfully updated the local access settings."


def get_ip_from_host(hostname):
    ipaddr = hostname
    try:
        result = socket.getaddrinfo(hostname, None)
        last_element = result[-1]
        ip_address = last_element[-1][0]
        if ip_address:
            ipaddr = ip_address
    except socket.gaierror:
        ipaddr = hostname
    except Exception:
        ipaddr = hostname
    return ipaddr


def get_chassis_device(module, rest_obj):
    key, value = None, None
    ipaddress = get_ip_from_host(module.params["hostname"])
    resp = rest_obj.invoke_request("GET", DOMAIN_URI)
    for data in resp.json_data["value"]:
        if ipaddress in data["PublicAddress"]:
            key, value = ("Id", data["DeviceId"])
            break
    else:
        module.fail_json(msg=FETCH_FAIL_MSG)
    return key, value


def check_domain_service(module, rest_obj):
    try:
        rest_obj.invoke_request("GET", DOMAIN_URI, api_timeout=5)
    except HTTPError as err:
        err_message = json.load(err)
        if err_message["error"]["@Message.ExtendedInfo"][0]["MessageId"] == "CGEN1006":
            module.fail_json(msg=DOMAIN_FAIL_MSG)
    return


def check_mode_validation(module, loc_resp):
    exist_config = {
        "EnableKvmAccess": loc_resp["EnableKvmAccess"], "EnableChassisDirect": loc_resp["EnableChassisDirect"],
        "EnableChassisPowerButton": loc_resp["EnableChassisPowerButton"],
        "EnableLcdOverridePin": loc_resp["EnableLcdOverridePin"], "LcdAccess": loc_resp["LcdAccess"],
        "LcdCustomString": loc_resp["LcdCustomString"], "LcdLanguage": loc_resp["LcdLanguage"]}
    quick_sync = loc_resp["QuickSync"]
    exist_quick_config = {
        "QuickSyncAccess": quick_sync["QuickSyncAccess"], "TimeoutLimit": quick_sync["TimeoutLimit"],
        "EnableInactivityTimeout": quick_sync["EnableInactivityTimeout"],
        "TimeoutLimitUnit": quick_sync["TimeoutLimitUnit"],
        "EnableReadAuthentication": quick_sync["EnableReadAuthentication"],
        "EnableQuickSyncWifi": quick_sync["EnableQuickSyncWifi"]}
    req_config, req_quick_config, payload = {}, {}, {}
    lcd_options, chassis_power = module.params.get("lcd"), module.params.get("chassis_power_button")
    if loc_resp["LcdPresence"] == "Present" and lcd_options is not None:
        req_config["LcdCustomString"] = lcd_options.get("user_defined")
        req_config["LcdAccess"] = lcd_options.get("lcd_access")
        req_config["LcdLanguage"] = lcd_options.get("lcd_language")
    req_config["EnableKvmAccess"] = module.params.get("enable_kvm_access")
    req_config["EnableChassisDirect"] = module.params.get("enable_chassis_direct_access")
    if chassis_power is not None:
        power_button = chassis_power["enable_chassis_power_button"]
        if power_button is False:
            chassis_pin = chassis_power.get("enable_lcd_override_pin")
            if chassis_pin is True:
                exist_config["LcdOverridePin"] = loc_resp["LcdOverridePin"]
                req_config["LcdOverridePin"] = chassis_power["disabled_button_lcd_override_pin"]
            req_config["EnableLcdOverridePin"] = chassis_pin
        req_config["EnableChassisPowerButton"] = power_button
    q_sync = module.params.get("quick_sync")
    if q_sync is not None and loc_resp["QuickSync"]["QuickSyncHardware"] == "Present":
        req_quick_config["QuickSyncAccess"] = q_sync.get("quick_sync_access")
        req_quick_config["EnableReadAuthentication"] = q_sync.get("enable_read_authentication")
        req_quick_config["EnableQuickSyncWifi"] = q_sync.get("enable_quick_sync_wifi")
        if q_sync.get("enable_inactivity_timeout") is True:
            time_limit, time_unit = q_sync.get("timeout_limit"), q_sync.get("timeout_limit_unit")
            if q_sync.get("timeout_limit_unit") == "MINUTES":
                time_limit, time_unit = time_limit * 60, "SECONDS"
            req_quick_config["TimeoutLimit"] = time_limit
            req_quick_config["TimeoutLimitUnit"] = time_unit
        req_quick_config["EnableInactivityTimeout"] = q_sync.get("enable_inactivity_timeout")
    req_config = dict([(k, v) for k, v in req_config.items() if v is not None])
    req_quick_config = dict([(k, v) for k, v in req_quick_config.items() if v is not None])
    cloned_req_config = copy.deepcopy(exist_config)
    cloned_req_config.update(req_config)
    cloned_req_quick_config = copy.deepcopy(exist_quick_config)
    cloned_req_quick_config.update(req_quick_config)
    diff_changes = [bool(set(exist_config.items()) ^ set(cloned_req_config.items())) or
                    bool(set(exist_quick_config.items()) ^ set(cloned_req_quick_config.items()))]
    if module.check_mode and any(diff_changes) is True:
        module.exit_json(msg=CHANGES_FOUND, changed=True)
    elif (module.check_mode and all(diff_changes) is False) or \
            (not module.check_mode and all(diff_changes) is False):
        module.exit_json(msg=NO_CHANGES_FOUND)
    payload.update(cloned_req_config)
    payload["QuickSync"] = cloned_req_quick_config
    payload["QuickSync"]["QuickSyncHardware"] = loc_resp["QuickSync"]["QuickSyncHardware"]
    payload["SettingType"] = "LocalAccessConfiguration"
    payload["LcdPresence"] = loc_resp["LcdPresence"]
    return payload


def get_device_details(rest_obj, module):
    device_id, tag = module.params.get("device_id"), module.params.get("device_service_tag")
    if device_id is None and tag is None:
        key, value = get_chassis_device(module, rest_obj)
        device_id = value
    else:
        key, value = ("Id", device_id) if device_id is not None else ("DeviceServiceTag", tag)
        param_value = "{0} eq {1}".format(key, value) if key == "Id" else "{0} eq '{1}'".format(key, value)
        resp = rest_obj.invoke_request("GET", DEVICE_URI, query_param={"$filter": param_value})
        resp_data = resp.json_data.get("value")
        rename_key = "id" if key == "Id" else "service tag"
        if not resp_data:
            module.fail_json(msg=DEVICE_FAIL_MSG.format(rename_key, value))
        if key == "DeviceServiceTag" and resp_data[0]["DeviceServiceTag"] == tag:
            device_id = resp_data[0]["Id"]
        elif key == "Id" and resp_data[0]["Id"] == device_id:
            device_id = resp_data[0]["Id"]
        else:
            module.fail_json(msg=DEVICE_FAIL_MSG.format(rename_key, value))
    try:
        loc_resp = rest_obj.invoke_request("GET", LAC_API.format(device_id))
    except HTTPError as err:
        err_message = json.load(err)
        error_msg = err_message.get('error', {}).get('@Message.ExtendedInfo')
        if error_msg and error_msg[0].get("MessageId") == "CGEN1004":
            module.fail_json(msg=LAC_FAIL_MSG)
    else:
        payload = check_mode_validation(module, loc_resp.json_data)
        final_resp = rest_obj.invoke_request("PUT", LAC_API.format(device_id), data=payload)
    return final_resp


def main():
    chassis_power = {
        "enable_chassis_power_button": {"type": "bool", "required": True},
        "enable_lcd_override_pin": {"type": "bool", "required": False},
        "disabled_button_lcd_override_pin": {"type": "str", "required": False, "no_log": True}}
    quick_sync_options = {
        "quick_sync_access": {"type": "str", "required": False, "choices": ["DISABLED", "READ_ONLY", "READ_WRITE"]},
        "enable_inactivity_timeout": {"type": "bool", "required": False},
        "timeout_limit": {"type": "int", "required": False},
        "timeout_limit_unit": {"type": "str", "required": False, "choices": ["SECONDS", "MINUTES"]},
        "enable_read_authentication": {"type": "bool", "required": False},
        "enable_quick_sync_wifi": {"type": "bool", "required": False}}
    lcd_options = {
        "lcd_access": {"type": "str", "required": False, "choices": ["VIEW_AND_MODIFY", "VIEW_ONLY", "DISABLED"]},
        "user_defined": {"type": "str", "required": False},
        "lcd_language": {"type": "str", "required": False}}
    specs = {
        "device_id": {"required": False, "type": "int"},
        "device_service_tag": {"required": False, "type": "str"},
        "enable_kvm_access": {"required": False, "type": "bool"},
        "enable_chassis_direct_access": {"required": False, "type": "bool"},
        "chassis_power_button": {
            "required": False, "type": "dict", "options": chassis_power,
            "required_if": [["enable_lcd_override_pin", True, ("disabled_button_lcd_override_pin",)]],
        },
        "quick_sync": {
            "required": False, "type": "dict", "options": quick_sync_options,
            "required_if": [["enable_inactivity_timeout", True, ("timeout_limit", "timeout_limit_unit")]]
        },
        "lcd": {
            "required": False, "type": "dict", "options": lcd_options,
        },
    }
    specs.update(ome_auth_params)
    module = AnsibleModule(
        argument_spec=specs,
        mutually_exclusive=[('device_id', 'device_service_tag')],
        required_one_of=[["enable_kvm_access", "enable_chassis_direct_access",
                          "chassis_power_button", "quick_sync", "lcd"]],
        supports_check_mode=True,
    )
    try:
        if not any([module.params.get("chassis_power_button"), module.params.get("quick_sync"),
                    module.params.get("lcd"), module.params.get("enable_kvm_access") is not None,
                    module.params.get("enable_chassis_direct_access") is not None]):
            module.fail_json(msg=CONFIG_FAIL_MSG)
        with RestOME(module.params, req_session=True) as rest_obj:
            check_domain_service(module, rest_obj)
            resp = get_device_details(rest_obj, module)
            resp_data = resp.json_data
            quick_sync = module.params.get("quick_sync")
            if quick_sync is not None and quick_sync.get("enable_inactivity_timeout") is True and \
                    quick_sync.get("timeout_limit_unit") == "MINUTES":
                resp_data["QuickSync"]["TimeoutLimit"] = int(resp_data["QuickSync"]["TimeoutLimit"] / 60)
                resp_data["QuickSync"]["TimeoutLimitUnit"] = "MINUTES"
            module.exit_json(msg=SUCCESS_MSG, local_access_settings=resp_data, changed=True)
    except HTTPError as err:
        module.exit_json(msg=str(err), error_info=json.load(err), failed=True)
    except URLError as err:
        module.exit_json(msg=str(err), unreachable=True)
    except (IOError, ValueError, SSLError, TypeError, ConnectionError, AttributeError, IndexError, KeyError, OSError) as err:
        module.fail_json(msg=str(err))


if __name__ == '__main__':
    main()
