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

# Copyright (c) 2021, Rainer Leber <rainerleber@gmail.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 (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r'''
---
module: sap_task_list_execute
short_description: Perform SAP Task list execution
version_added: "3.5.0"
description:
  - The C(sap_task_list_execute) module depends on C(pyrfc) Python library (version 2.4.0 and upwards).
    Depending on distribution you are using, you may need to install additional packages to
    have these available.
  - Tasks in the task list which requires manual activities will be confirmed automatically.
  - This module will use the RFC package C(STC_TM_API).

requirements:
  - pyrfc >= 2.4.0
  - xmltodict

options:
  conn_username:
    description: The required username for the SAP system.
    required: true
    type: str
  conn_password:
    description: The required password for the SAP system.
    required: true
    type: str
  host:
    description: The required host for the SAP system. Can be either an FQDN or IP Address.
    required: true
    type: str
  sysnr:
    description:
      - The system number of the SAP system.
      - You must quote the value to ensure retaining the leading zeros.
    default: '00'
    type: str
  client:
    description:
      - The client number to connect to.
      - You must quote the value to ensure retaining the leading zeros.
    default: '000'
    type: str
  task_to_execute:
    description: The task list which will be executed.
    required: true
    type: str
  task_parameters:
    description:
      - The tasks and the parameters for execution.
      - If the task list do not need any parameters. This could be empty.
      - If only specific tasks from the task list should be executed.
        The tasks even when no parameter is needed must be provided.
        Alongside with the module parameter I(task_skip=true).
    type: list
    elements: dict
    suboptions:
      TASKNAME:
        description: The name of the task in the task list.
        type: str
        required: true
      FIELDNAME:
        description: The name of the field of the task.
        type: str
      VALUE:
        description: The value which have to be set.
        type: raw
  task_settings:
    description:
      - Setting for the execution of the task list. This can be the following as in TCODE SE80 described.
          Check Mode C(CHECKRUN), Background Processing Active C(BATCH) (this is the default value),
          Asynchronous Execution C(ASYNC), Trace Mode C(TRACE), Server Name C(BATCH_TARGET).
    default: ['BATCH']
    type: list
    elements: str
  task_skip:
    description:
      - If this parameter is C(true) not defined tasks in I(task_parameters) are skipped.
      - This could be the case when only certain tasks should run from the task list.
    default: false
    type: bool

notes:
    - Does not support C(check_mode).
author:
    - Rainer Leber (@rainerleber)
'''

EXAMPLES = r'''
# Pass in a message
- name: Test task execution
  community.general.sap_task_list_execute:
    conn_username: DDIC
    conn_password: Passwd1234
    host: 10.1.8.10
    sysnr: '01'
    client: '000'
    task_to_execute: SAP_BASIS_SSL_CHECK
    task_settings: batch

- name: Pass in input parameters
  community.general.sap_task_list_execute:
    conn_username: DDIC
    conn_password: Passwd1234
    host: 10.1.8.10
    sysnr: '00'
    client: '000'
    task_to_execute: SAP_BASIS_SSL_CHECK
    task_parameters :
      - { 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', 'FIELDNAME': 'P_OPT2', 'VALUE': 'X' }
      - TASKNAME: CL_STCT_CHECK_SEC_CRYPTO
        FIELDNAME: P_OPT3
        VALUE: X
    task_settings: batch

# Exported environement variables.
- name: Hint if module will fail with error message like ImportError libsapnwrfc.so...
  community.general.sap_task_list_execute:
    conn_username: DDIC
    conn_password: Passwd1234
    host: 10.1.8.10
    sysnr: '00'
    client: '000'
    task_to_execute: SAP_BASIS_SSL_CHECK
    task_settings: batch
  environment:
    SAPNWRFC_HOME: /usr/local/sap/nwrfcsdk
    LD_LIBRARY_PATH: /usr/local/sap/nwrfcsdk/lib
'''

RETURN = r'''
msg:
  description: A small execution description.
  type: str
  returned: always
  sample: 'Successful'
out:
  description: A complete description of the executed tasks. If this is available.
  type: list
  elements: dict
  returned: on success
  sample: [...,{
              "LOG": {
                  "STCTM_S_LOG": [
                      {
                          "ACTIVITY": "U_CONFIG",
                          "ACTIVITY_DESCR": "Configuration changed",
                          "DETAILS": null,
                          "EXEC_ID": "20210728184903.815739",
                          "FIELD": null,
                          "ID": "STC_TASK",
                          "LOG_MSG_NO": "000000",
                          "LOG_NO": null,
                          "MESSAGE": "For radiobutton group ICM too many options are set; choose only one option",
                          "MESSAGE_V1": "ICM",
                          "MESSAGE_V2": null,
                          "MESSAGE_V3": null,
                          "MESSAGE_V4": null,
                          "NUMBER": "048",
                          "PARAMETER": null,
                          "PERIOD": "M",
                          "PERIOD_DESCR": "Maintenance",
                          "ROW": "0",
                          "SRC_LINE": "170",
                          "SRC_OBJECT": "CL_STCTM_REPORT_UI            IF_STCTM_UI_TASK~SET_PARAMETERS",
                          "SYSTEM": null,
                          "TIMESTMP": "20210728184903",
                          "TSTPNM": "DDIC",
                          "TYPE": "E"
                      },...
                    ]}}]
'''

from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.json_utils import json
import traceback
try:
    from pyrfc import Connection
except ImportError:
    HAS_PYRFC_LIBRARY = False
    PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
    HAS_PYRFC_LIBRARY = True
    PYRFC_LIBRARY_IMPORT_ERROR = None
try:
    import xmltodict
except ImportError:
    HAS_XMLTODICT_LIBRARY = False
    XMLTODICT_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
    HAS_XMLTODICT_LIBRARY = True
    XMLTODICT_LIBRARY_IMPORT_ERROR = None


def call_rfc_method(connection, method_name, kwargs):
    # PyRFC call function
    return connection.call(method_name, **kwargs)


def process_exec_settings(task_settings):
    # processes task settings to objects
    exec_settings = {}
    for settings in task_settings:
        temp_dict = {settings.upper(): 'X'}
        for key, value in temp_dict.items():
            exec_settings[key] = value
    return exec_settings


def xml_to_dict(xml_raw):
    try:
        xml_parsed = xmltodict.parse(xml_raw, dict_constructor=dict)
        xml_dict = xml_parsed['asx:abap']['asx:values']['SESSION']['TASKLIST']
    except KeyError:
        xml_dict = "No logs available."
    return xml_dict


def run_module():

    params_spec = dict(
        TASKNAME=dict(type='str', required=True),
        FIELDNAME=dict(type='str'),
        VALUE=dict(type='raw'),
    )

    # define available arguments/parameters a user can pass to the module
    module = AnsibleModule(
        argument_spec=dict(
            # values for connection
            conn_username=dict(type='str', required=True),
            conn_password=dict(type='str', required=True, no_log=True),
            host=dict(type='str', required=True),
            sysnr=dict(type='str', default="00"),
            client=dict(type='str', default="000"),
            # values for execution tasks
            task_to_execute=dict(type='str', required=True),
            task_parameters=dict(type='list', elements='dict', options=params_spec),
            task_settings=dict(type='list', elements='str', default=['BATCH']),
            task_skip=dict(type='bool', default=False),
        ),
        supports_check_mode=False,
    )
    result = dict(changed=False, msg='', out={})

    params = module.params

    username = params['conn_username'].upper()
    password = params['conn_password']
    host = params['host']
    sysnr = params['sysnr']
    client = params['client']

    task_parameters = params['task_parameters']
    task_to_execute = params['task_to_execute']
    task_settings = params['task_settings']
    task_skip = params['task_skip']

    if not HAS_PYRFC_LIBRARY:
        module.fail_json(
            msg=missing_required_lib('pyrfc'),
            exception=PYRFC_LIBRARY_IMPORT_ERROR)

    if not HAS_XMLTODICT_LIBRARY:
        module.fail_json(
            msg=missing_required_lib('xmltodict'),
            exception=XMLTODICT_LIBRARY_IMPORT_ERROR)

    # basic RFC connection with pyrfc
    try:
        conn = Connection(user=username, passwd=password, ashost=host, sysnr=sysnr, client=client)
    except Exception as err:
        result['error'] = str(err)
        result['msg'] = 'Something went wrong connecting to the SAP system.'
        module.fail_json(**result)

    try:
        raw_params = call_rfc_method(conn, 'STC_TM_SCENARIO_GET_PARAMETERS',
                                     {'I_SCENARIO_ID': task_to_execute})
    except Exception as err:
        result['error'] = str(err)
        result['msg'] = 'The task list does not exsist.'
        module.fail_json(**result)
    exec_settings = process_exec_settings(task_settings)
    # initialize session task
    session_init = call_rfc_method(conn, 'STC_TM_SESSION_BEGIN',
                                   {'I_SCENARIO_ID': task_to_execute,
                                    'I_INIT_ONLY': 'X'})
    # Confirm Tasks which requires manual activities from Task List Run
    for task in raw_params['ET_PARAMETER']:
        call_rfc_method(conn, 'STC_TM_TASK_CONFIRM',
                        {'I_SESSION_ID': session_init['E_SESSION_ID'],
                         'I_TASKNAME': task['TASKNAME']})
    if task_skip:
        for task in raw_params['ET_PARAMETER']:
            call_rfc_method(conn, 'STC_TM_TASK_SKIP',
                            {'I_SESSION_ID': session_init['E_SESSION_ID'],
                             'I_TASKNAME': task['TASKNAME'], 'I_SKIP_DEP_TASKS': 'X'})
    # unskip defined tasks and set parameters
    if task_parameters is not None:
        for task in task_parameters:
            call_rfc_method(conn, 'STC_TM_TASK_UNSKIP',
                            {'I_SESSION_ID': session_init['E_SESSION_ID'],
                             'I_TASKNAME': task['TASKNAME'], 'I_UNSKIP_DEP_TASKS': 'X'})

        call_rfc_method(conn, 'STC_TM_SESSION_SET_PARAMETERS',
                        {'I_SESSION_ID': session_init['E_SESSION_ID'],
                         'IT_PARAMETER': task_parameters})
    # start the task
    try:
        session_start = call_rfc_method(conn, 'STC_TM_SESSION_RESUME',
                                        {'I_SESSION_ID': session_init['E_SESSION_ID'],
                                         'IS_EXEC_SETTINGS': exec_settings})
    except Exception as err:
        result['error'] = str(err)
        result['msg'] = 'Something went wrong. See error.'
        module.fail_json(**result)
    # get task logs because the execution may successfully but the tasks shows errors or warnings
    # returned value is ABAPXML https://help.sap.com/doc/abapdocu_755_index_htm/7.55/en-US/abenabap_xslt_asxml_general.htm
    session_log = call_rfc_method(conn, 'STC_TM_SESSION_GET_LOG',
                                  {'I_SESSION_ID': session_init['E_SESSION_ID']})

    task_list = xml_to_dict(session_log['E_LOG'])

    result['changed'] = True
    result['msg'] = session_start['E_STATUS_DESCR']
    result['out'] = task_list

    module.exit_json(**result)


def main():
    run_module()


if __name__ == '__main__':
    main()
