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

#
# Dell OpenManage Ansible Modules
# Version 9.3.0
# Copyright (C) 2021-2025 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_network_services
short_description: Configure chassis network services settings on OpenManage Enterprise Modular
description: This module allows to configure the network services on OpenManage Enterprise Modular.
version_added: "4.3.0"
extends_documentation_fragment:
  - dellemc.openmanage.omem_auth_options
options:
  device_id:
    type: int
    description:
      - The ID of the chassis for which the settings need to be updated.
      - If the device ID is not specified, this module updates the network services 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 setting needs to be updated.
      - If the device service tag is not specified, this module updates the network
        services settings for the I(hostname).
      - I(device_service_tag) is mutually exclusive with I(device_id).
  snmp_settings:
    type: dict
    description: The settings for SNMP configuration.
    suboptions:
      enabled:
        type: bool
        required: true
        description: Enables or disables the SNMP settings.
      port_number:
        type: int
        description: The SNMP port number.
      community_name:
        type: str
        description:
          - The SNMP community string.
          - Required when I(enabled) is C(true).
  ssh_settings:
    type: dict
    description: The settings for SSH configuration.
    suboptions:
      enabled:
        required: true
        type: bool
        description: Enables or disables the SSH settings.
      port_number:
        type: int
        description: The port number for SSH service.
      max_sessions:
        type: int
        description: Number of SSH sessions.
      max_auth_retries:
        type: int
        description: The number of retries when the SSH session fails.
      idle_timeout:
        type: float
        description: SSH idle timeout in minutes.
  remote_racadm_settings:
    type: dict
    description: The settings for remote RACADM configuration.
    suboptions:
      enabled:
        type: bool
        required: true
        description: Enables or disables the remote RACADM settings.
requirements:
  - "python >= 3.9.6"
author:
  - "Felix Stephen (@felixs88)"
notes:
  - Run this module from a system that has direct access to Dell OpenManage Enterprise Modular.
  - This module supports C(check_mode).
"""

EXAMPLES = """
---
- name: Update network services settings of a chassis using the device ID
  dellemc.openmanage.ome_device_network_services:
    hostname: "192.168.0.1"
    username: "username"
    password: "password"
    ca_path: "/path/to/ca_cert.pem"
    device_id: 25011
    snmp_settings:
      enabled: true
      port_number: 161
      community_name: public
    ssh_settings:
      enabled: false
    remote_racadm_settings:
      enabled: false

- name: Update network services settings of a chassis using the device service tag.
  dellemc.openmanage.ome_device_network_services:
    hostname: "192.168.0.2"
    username: "username"
    password: "password"
    ca_path: "/path/to/ca_cert.pem"
    device_service_tag: GHRT2RL
    snmp_settings:
      enabled: false
    ssh_settings:
      enabled: true
      port_number: 22
      max_sessions: 1
      max_auth_retries: 3
      idle_timeout: 1
    remote_racadm_settings:
      enabled: false

- name: Update network services settings of the host chassis.
  dellemc.openmanage.ome_device_network_services:
    hostname: "192.168.0.3"
    username: "username"
    password: "password"
    ca_path: "/path/to/ca_cert.pem"
    snmp_settings:
      enabled: false
    ssh_settings:
      enabled: false
    remote_racadm_settings:
      enabled: true
"""

RETURN = """
---
msg:
  type: str
  description: Overall status of the network services settings.
  returned: always
  sample: "Successfully updated the network services settings."
network_services_details:
  type: dict
  description: returned when network services settings are updated successfully.
  returned: success
  sample: {
    "EnableRemoteRacadm": true,
    "SettingType": "NetworkServices",
    "SnmpConfiguration": {
      "PortNumber": 161,
      "SnmpEnabled": true,
      "SnmpV1V2Credential": {
        "CommunityName": "public"
      }
    },
    "SshConfiguration": {
      "IdleTimeout": 60,
      "MaxAuthRetries": 3,
      "MaxSessions": 1,
      "PortNumber": 22,
      "SshEnabled": false
    }
  }
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": "CAPP1042",
          "RelatedProperties": [],
          "Message": "Unable to update the network configuration because the SNMP PortNumber is already in use.",
          "MessageArgs": ["SNMP PortNumber"],
          "Severity": "Informational",
          "Resolution": "Enter a different port number and retry the operation.",
        }
      ]
    }
  }
"""


import json
import socket
from ssl import SSLError
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, OmeAnsibleModule

DOMAIN_URI = "ManagementDomainService/Domains"
DEVICE_URI = "DeviceService/Devices"
NETWORK_SERVICE_API = "DeviceService/Devices({0})/Settings('NetworkServices')"
CONFIG_FAIL_MSG = "one of the following is required: snmp_settings, ssh_settings, remote_racadm_settings"
DOMAIN_FAIL_MSG = "The device location settings operation is supported only on " \
                  "OpenManage Enterprise Modular."
FETCH_FAIL_MSG = "Failed to retrieve the device information."
DEVICE_FAIL_MSG = "Unable to complete the operation because the entered target device {0} '{1}' is invalid."
NETWORK_SERVICE_FAIL_MSG = "Unable to complete the operation because the network services 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 network services settings."


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.loads(err)
        if err_message["error"]["@Message.ExtendedInfo"][0]["MessageId"] == "CGEN1006":
            module.fail_json(msg=DOMAIN_FAIL_MSG)
    return


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_mode_validation(module, loc_data, rest_obj):
    req_snmp, req_ssh, req_comm_str, req_racadm = {}, {}, {}, {}
    exist_snmp, exist_ssh, exist_comm_str, exist_racadm = {}, {}, {}, {}
    payload = {"SettingType": "NetworkServices"}
    snmp_enabled = module.params.get("snmp_settings")
    if snmp_enabled is not None and snmp_enabled["enabled"] is True:
        req_snmp.update({"SnmpEnabled": snmp_enabled["enabled"]})
        req_comm_str.update({"CommunityName": module.params["snmp_settings"]["community_name"]})
        exist_snmp.update({"SnmpEnabled": loc_data["SnmpConfiguration"]["SnmpEnabled"]})
        exist_comm_str.update({"CommunityName": loc_data["SnmpConfiguration"]["SnmpV1V2Credential"]["CommunityName"]})
    elif snmp_enabled is not None and snmp_enabled["enabled"] is False:
        req_snmp.update({"SnmpEnabled": snmp_enabled["enabled"]})
        exist_snmp.update({"SnmpEnabled": loc_data["SnmpConfiguration"]["SnmpEnabled"]})

    if snmp_enabled is not None and snmp_enabled["enabled"] is True and snmp_enabled.get("port_number") is not None:
        req_snmp.update({"PortNumber": snmp_enabled.get("port_number")})
        exist_snmp.update({"PortNumber": loc_data["SnmpConfiguration"]["PortNumber"]})
    ssh_enabled = module.params.get("ssh_settings")
    if ssh_enabled is not None and ssh_enabled["enabled"] is True:
        req_ssh.update({"SshEnabled": ssh_enabled["enabled"]})
        exist_ssh.update({"SshEnabled": loc_data["SshConfiguration"]["SshEnabled"]})
    elif ssh_enabled is not None and ssh_enabled["enabled"] is False:
        req_ssh.update({"SshEnabled": ssh_enabled["enabled"]})
        exist_ssh.update({"SshEnabled": loc_data["SshConfiguration"]["SshEnabled"]})

    if ssh_enabled is not None and ssh_enabled["enabled"] is True and ssh_enabled.get("port_number") is not None:
        req_ssh.update({"PortNumber": module.params["ssh_settings"]["port_number"]})
        exist_ssh.update({"PortNumber": loc_data["SshConfiguration"]["PortNumber"]})
    if ssh_enabled is not None and ssh_enabled["enabled"] is True and ssh_enabled.get("max_sessions") is not None:
        req_ssh.update({"MaxSessions": module.params["ssh_settings"]["max_sessions"]})
        exist_ssh.update({"MaxSessions": loc_data["SshConfiguration"]["MaxSessions"]})
    if ssh_enabled is not None and ssh_enabled["enabled"] is True and ssh_enabled.get("max_auth_retries") is not None:
        req_ssh.update({"MaxAuthRetries": module.params["ssh_settings"]["max_auth_retries"]})
        exist_ssh.update({"MaxAuthRetries": loc_data["SshConfiguration"]["MaxAuthRetries"]})
    if ssh_enabled is not None and ssh_enabled["enabled"] is True and ssh_enabled.get("idle_timeout") is not None:
        req_ssh.update({"IdleTimeout": int(module.params["ssh_settings"]["idle_timeout"] * 60)})
        exist_ssh.update({"IdleTimeout": int(loc_data["SshConfiguration"]["IdleTimeout"])})
    recadm_enabled = module.params.get("remote_racadm_settings")
    if recadm_enabled is not None and recadm_enabled["enabled"] is True:
        req_racadm = {"EnableRemoteRacadm": recadm_enabled["enabled"]}
        exist_racadm = {"EnableRemoteRacadm": loc_data["EnableRemoteRacadm"]}
    elif recadm_enabled is not None and recadm_enabled["enabled"] is False:
        req_racadm = {"EnableRemoteRacadm": recadm_enabled["enabled"]}
        exist_racadm = {"EnableRemoteRacadm": loc_data["EnableRemoteRacadm"]}
    changes = [bool(set(req_snmp.items()) ^ set(exist_snmp.items())) or
               bool(set(req_ssh.items()) ^ set(exist_ssh.items())) or
               bool(set(req_comm_str.items()) ^ set(exist_comm_str.items())) or
               bool(set(req_racadm.items()) ^ set(exist_racadm.items()))]
    if module.check_mode and any(changes) is True:
        loc_data["SshConfiguration"]["IdleTimeout"] = loc_data["SshConfiguration"]["IdleTimeout"] / 60
        module.exit_json(msg=CHANGES_FOUND, changed=True)
    elif module.check_mode and all(changes) is False:
        loc_data["SshConfiguration"]["IdleTimeout"] = loc_data["SshConfiguration"]["IdleTimeout"] / 60
        module.exit_json(msg=NO_CHANGES_FOUND)
    elif not module.check_mode and all(changes) is False:
        loc_data["SshConfiguration"]["IdleTimeout"] = loc_data["SshConfiguration"]["IdleTimeout"] / 60
        module.exit_json(msg=NO_CHANGES_FOUND)
    else:
        payload.update(loc_data)
        payload["SnmpConfiguration"].update(req_snmp) if req_snmp else None
        payload["SnmpConfiguration"]["SnmpV1V2Credential"].update(req_comm_str) if req_comm_str else None
        payload["SshConfiguration"].update(req_ssh) if req_ssh else None
        payload.update(req_racadm) if req_racadm else None
    return payload


def fetch_device_details(module, rest_obj):
    device_id, tag, final_resp = 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", NETWORK_SERVICE_API.format(device_id))
    except HTTPError as err:
        if err.code == 404:
            module.fail_json(msg=NETWORK_SERVICE_FAIL_MSG)
        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=NETWORK_SERVICE_FAIL_MSG)
    else:
        loc_resp_data = rest_obj.strip_substr_dict(loc_resp.json_data)
        payload = check_mode_validation(module, loc_resp_data, rest_obj)
        final_resp = rest_obj.invoke_request("PUT", NETWORK_SERVICE_API.format(device_id), data=payload)
    return final_resp


def main():
    snmp_options = {"enabled": {"type": "bool", "required": True},
                    "port_number": {"type": "int", "required": False},
                    "community_name": {"type": "str", "required": False}}
    ssh_options = {"enabled": {"type": "bool", "required": True},
                   "port_number": {"type": "int", "required": False},
                   "max_sessions": {"type": "int", "required": False},
                   "max_auth_retries": {"type": "int", "required": False},
                   "idle_timeout": {"type": "float", "required": False}}
    racadm_options = {"enabled": {"type": "bool", "required": True}}
    specs = {
        "device_id": {"required": False, "type": "int"},
        "device_service_tag": {"required": False, "type": "str"},
        "snmp_settings": {"type": "dict", "required": False, "options": snmp_options,
                          "required_if": [["enabled", True, ("community_name",)]]},
        "ssh_settings": {"type": "dict", "required": False, "options": ssh_options},
        "remote_racadm_settings": {"type": "dict", "required": False, "options": racadm_options},
    }

    module = OmeAnsibleModule(
        argument_spec=specs,
        mutually_exclusive=[('device_id', 'device_service_tag')],
        required_one_of=[["snmp_settings", "ssh_settings", "remote_racadm_settings"]],
        supports_check_mode=True,
    )
    if not any([module.params.get("snmp_settings"), module.params.get("ssh_settings"),
                module.params.get("remote_racadm_settings")]):
        module.fail_json(msg=CONFIG_FAIL_MSG)
    try:
        with RestOME(module.params, req_session=True) as rest_obj:
            check_domain_service(module, rest_obj)
            resp = fetch_device_details(module, rest_obj)
            resp_data = resp.json_data
            resp_data["SshConfiguration"]["IdleTimeout"] = resp_data["SshConfiguration"]["IdleTimeout"] / 60
            module.exit_json(msg=SUCCESS_MSG, network_services_details=resp_data, changed=True)
    except HTTPError as err:
        module.fail_json(msg=str(err), error_info=json.load(err))
    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()
