#!/usr/bin/python

# Copyright (c) 2022-2026 The Project Contributors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# For a detailed list of copyright holders and contribution history,
# please refer to the CONTRIBUTORS.md file in the project root.

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r'''
---
module: sap_control_exec

short_description: Ansible Module to execute SAPCONTROL

version_added: "1.1.0"

description:
    - Provides support for sapstartsrv formaly known as sapcontrol
    - A complete information of all functions and the parameters can be found here
      U(https://www.sap.com/documents/2016/09/0a40e60d-8b7c-0010-82c7-eda71af511fa.html)
    - When hostname is 'localhost', sysnr is set and no username/password are provided, the module will attempt
      to use local Unix socket authentication (which works with 'become' privilege escalation).

options:
    sysnr:
        description:
            - The system number of the instance.
        required: false
        type: str
    port:
        description:
            - The port number of the sapstartsrv.
            - If provided, the module will use always use http connection instead of local socket.
        required: false
        type: int
    username:
        description:
            - The username to connect to the sapstartsrv.
        required: false
        type: str
    password:
        description:
            - The password to connect to the sapstartsrv.
        required: false
        type: str
    hostname:
        description:
            - The hostname to connect to the sapstartsrv.
            - Could be an IP address, FQDN or hostname.
        required: false
        default: localhost
        type: str
    function:
        description:
        - The function to execute.
        required: true
        choices:
        - Start
        - Stop
        - Shutdown
        - InstanceStart
        - InstanceStop
        - Bootstrap
        - ParameterValue
        - GetProcessList
        - GetProcessList2
        - GetStartProfile
        - GetTraceFile
        - GetAlertTree
        - GetAlerts
        - RestartService
        - StopService
        - GetEnvironment
        - ListDeveloperTraces
        - ListLogFiles
        - ReadDeveloperTrace
        - ReadLogFile
        - AnalyseLogFile
        - ConfigureLogFileList
        - GetLogFileList
        - RestartInstance
        - SendSignal
        - GetVersionInfo
        - GetQueueStatistic
        - GetInstanceProperties
        - OSExecute
        - AnalyseLogFiles
        - GetAccessPointList
        - GetSystemInstanceList
        - StartSystem
        - StopSystem
        - RestartSystem
        - AccessCheck
        - GetProcessParameter
        - SetProcessParameter
        - SetProcessParameter2
        - ShmDetach
        - CreateSnapshot
        - ReadSnapshot
        - ListSnapshots
        - DeleteSnapshots
        - RequestLogonFile
        - GetNetworkId
        - GetSecNetworkId
        - UpdateSystem
        - GetSystemUpdateList
        - UpdateSCSInstance
        - ABAPReadSyslog
        - ABAPReadRawSyslog
        - ABAPGetWPTable
        - ABAPAcknoledgeAlerts
        - CMGetThreadList
        - ICMGetConnectionList
        - ICMGetCacheEntries
        - ICMGetProxyConnectionList
        - WebDispGetServerList
        - WebDispGetGroupList
        - WebDispGetVirtHostList
        - WebDispGeUrlPrefixList
        - EnqGetLockTable
        - EnqRemoveLocks
        - EnqGetStatistic
        type: str
    parameter:
        description:
            - The parameter to pass to the function.
            - Specific functions require complex parameters to be defined. See example for InstanceStart.
        required: false
        type: raw
    force:
        description:
            - Forces the execution of the function C(Stop).
        required: false
        default: false
        type: bool
author:
    - Rainer Leber (@RainerLeber)
    - Robert Kraemer (@rkpobe)
notes:
    - Does not support C(check_mode).
'''

EXAMPLES = r"""
- name: GetProcessList with sysnr
  community.sap_libs.sap_control_exec:
    hostname: 192.168.8.15
    sysnr: "01"
    function: GetProcessList

- name: GetProcessList with custom port
  community.sap_libs.sap_control_exec:
    hostname: 192.168.8.15
    function: GetProcessList
    port: 50113

- name: ParameterValue with authentication
  community.sap_libs.sap_control_exec:
    hostname: 192.168.8.15
    sysnr: "01"
    username: hdbadm
    password: test1234
    function: ParameterValue
    parameter: ztta

- name: GetVersionInfo using local Unix socket (requires become)
  community.sap_libs.sap_control_exec:
    sysnr: "00"
    function: GetVersionInfo
  become: true

- name: GetProcessList using local Unix socket as SAP admin user
  community.sap_libs.sap_control_exec:
    sysnr: "00"
    function: GetProcessList
  become: true
  become_user: "{{ sap_sid | lower }}adm"

- name: InstanceStart with complex parameter
  community.sap_libs.sap_control_exec:
    sysnr: "00"
    function: InstanceStart
  become: true
  parameter:
    host: "s4hana"
    nr: "00"
"""

RETURN = r'''
msg:
    description: Success-message with functionname.
    type: str
    returned: always
    sample: 'Succesful execution of: GetProcessList'
out:
    description: The full output of the required function.
    type: list
    elements: dict
    returned: always
    sample: [{
            "item": [
                {
                    "description": "MessageServer",
                    "dispstatus": "SAPControl-GREEN",
                    "elapsedtime": "412:30:50",
                    "name": "msg_server",
                    "pid": 70643,
                    "starttime": "2022 03 13 15:22:42",
                    "textstatus": "Running"
                },
                {
                    "description": "EnqueueServer",
                    "dispstatus": "SAPControl-GREEN",
                    "elapsedtime": "412:30:50",
                    "name": "enserver",
                    "pid": 70644,
                    "starttime": "2022 03 13 15:22:42",
                    "textstatus": "Running"
                },
                {
                    "description": "Gateway",
                    "dispstatus": "SAPControl-GREEN",
                    "elapsedtime": "412:30:50",
                    "name": "gwrd",
                    "pid": 70645,
                    "starttime": "2022 03 13 15:22:42",
                    "textstatus": "Running"
                }
                ]
            }]
'''

from ansible.module_utils.basic import AnsibleModule, missing_required_lib

from ..module_utils.sapstartsrv_client import (
    HAS_SUDS_LIBRARY,
    SUDS_LIBRARY_IMPORT_ERROR,
    call_sap_control as connection,
    recursive_dict,
    is_read_only_function,
    requires_force,
)


def choices():
    retlist = ["Start", "Stop", "Shutdown", "InstanceStart", "InstanceStop", "Bootstrap", "ParameterValue", "GetProcessList",
               "GetProcessList2", "GetStartProfile", "GetTraceFile", "GetAlertTree", "GetAlerts", "RestartService",
               "StopService", "GetEnvironment", "ListDeveloperTraces", "ListLogFiles", "ReadDeveloperTrace", "ReadLogFile",
               "AnalyseLogFile", "ConfigureLogFileList", "GetLogFileList", "RestartInstance", "SendSignal", "GetVersionInfo",
               "GetQueueStatistic", "GetInstanceProperties", "OSExecute", "AnalyseLogFiles", "GetAccessPointList",
               "GetSystemInstanceList", "StartSystem", "StopSystem", "RestartSystem", "AccessCheck", "GetProcessParameter",
               "SetProcessParameter", "SetProcessParameter2", "ShmDetach", "CreateSnapshot", "ReadSnapshot", "ListSnapshots",
               "DeleteSnapshots", "RequestLogonFile", "GetNetworkId", "GetSecNetworkId", "UpdateSystem", "GetSystemUpdateList",
               "UpdateSCSInstance", "ABAPReadSyslog", "ABAPReadRawSyslog", "ABAPGetWPTable", "ABAPAcknoledgeAlerts",
               "CMGetThreadList", "ICMGetConnectionList", "ICMGetCacheEntries", "ICMGetProxyConnectionList",
               "WebDispGetServerList", "WebDispGetGroupList", "WebDispGetVirtHostList", "WebDispGeUrlPrefixList",
               "EnqGetLockTable", "EnqRemoveLocks", "EnqGetStatistic"]
    return retlist


def main():
    module = AnsibleModule(
        argument_spec=dict(
            sysnr=dict(type='str', required=False),
            port=dict(type='int', required=False),
            username=dict(type='str', required=False),
            password=dict(type='str', no_log=True, required=False),
            hostname=dict(type='str', default="localhost"),
            function=dict(type='str', required=True, choices=choices()),
            parameter=dict(type='raw', required=False),  # raw will allow dict or string.
            force=dict(type='bool', default=False),
        ),
        # Remove strict requirements to allow local mode
        required_one_of=[('sysnr', 'port')],
        mutually_exclusive=[('sysnr', 'port')],
        supports_check_mode=False,
    )
    result = dict(changed=False, msg='', out=[], error='')  # Default out to list for consistent return type.
    params = module.params

    sysnr = params['sysnr']
    port = params['port']
    username = params['username']
    password = params['password']
    hostname = params['hostname']
    function = params['function']
    parameter = params['parameter']
    force = params['force']

    if not HAS_SUDS_LIBRARY:
        module.fail_json(
            msg=missing_required_lib('suds'),
            exception=SUDS_LIBRARY_IMPORT_ERROR)

    # Validate arguments
    if sysnr is None and port is None:
        module.fail_json(msg="Either 'sysnr' or 'port' must be provided")

    if sysnr is not None and port is not None:
        module.fail_json(msg="'sysnr' and 'port' are mutually exclusive")

    if requires_force(function) and force is False:
        module.fail_json(msg="Function '{0}' requires force: True".format(function))

    if function == "StartSystem":
        parameter = dict(waittimeout=0)
    elif function == "StopSystem" or function == "RestartSystem":
        parameter = dict(waittimeout=0, softtimeout=0)

    # Determine if we should use local Unix socket connection
    # Use local socket connection if hostname is localhost and no username/password provided
    # True: Socket connection, False: SOAP connection
    is_socket = (hostname == "localhost" and
                 username is None and
                 password is None and
                 sysnr is not None)

    if port is None:
        try:
            if is_socket:
                result['connection_type'] = 'socket'
                result['connection_url'] = "http://localhost/sapcontrol?wsdl"

                result_conn = connection(hostname, None, username, password, function, parameter, sysnr=sysnr, is_socket=True)
            else:
                result['connection_type'] = 'soap'

                # Try HTTPS and HTTP ports
                try:
                    result['connection_url'] = 'http://{0}:5{1}14/sapcontrol?wsdl'.format(hostname, str(sysnr).zfill(2))
                    result_conn = connection(hostname, "5{0}14".format((sysnr).zfill(2)), username, password, function, parameter, sysnr)
                except Exception:
                    result['connection_url'] = 'http://{0}:5{1}13/sapcontrol?wsdl'.format(hostname, str(sysnr).zfill(2))
                    result_conn = connection(hostname, "5{0}13".format((sysnr).zfill(2)), username, password, function, parameter, sysnr)
        except Exception as err:
            if "already started" in str(err).lower():
                already_started_msg = "Function {0} returned that Instance is already started.".format(function)
                result_conn = ({"status": "already_started", "msg": already_started_msg})
            else:
                result['error'] = str(err)
    else:
        result['connection_type'] = 'soap'
        result['connection_url'] = 'http://{0}:{1}/sapcontrol?wsdl'.format(hostname, port)
        try:
            result_conn = connection(hostname, port, username, password, function, parameter, sysnr, is_socket=False)
        except Exception as err:
            if "already started" in str(err).lower():
                already_started_msg = "Function {0} returned that Instance is already started.".format(function)
                result_conn = ({"status": "already_started", "msg": already_started_msg})
            else:
                result['error'] = str(err)

    if result['error'] != '':
        result['msg'] = 'Function execution has failed. See error for more details.'
        module.fail_json(**result)

    conn_result = result_conn

    # Ensure that we run recursive_dict only on dict and leave it for idempotent functions.
    if isinstance(conn_result, dict) and conn_result.get('status') == 'already_started':
        result['changed'] = False
        result['msg'] = conn_result.get('msg')
        result['out'] = [None]  # Ensure we return same content as Start and Stop functions for consistency
    else:
        result['changed'] = not is_read_only_function(function)
        result['msg'] = "Successful execution of function: " + function
        returned_data = recursive_dict(conn_result) if conn_result is not None else conn_result
        result['out'] = [returned_data]

    module.exit_json(**result)


if __name__ == '__main__':
    main()
