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


# (c) 2020, John Westcott IV <john.westcott.iv@redhat.com>
# 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


ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'}

DOCUMENTATION = '''
---
module: workflow_job_template_node
author: "John Westcott IV (@john-westcott-iv)"
short_description: create, update, or destroy Automation Platform Controller workflow job template nodes.
description:
    - Create, update, or destroy Automation Platform Controller workflow job template nodes.
    - Use this to build a graph for a workflow, which dictates what the workflow runs.
    - You can create nodes first, and link them afterwards, and not worry about ordering.
      For failsafe referencing of a node, specify identifier, WFJT, and organization.
      With those specified, you can choose to modify or not modify any other parameter.
options:
    extra_data:
      description:
        - Variables to apply at launch time.
        - Will only be accepted if job template prompts for vars or has a survey asking for those vars.
      type: dict
    inventory:
      description:
        - Name, ID, or named URL of the Inventory applied as a prompt, if job template prompts for inventory
      type: str
    scm_branch:
      description:
        - SCM branch applied as a prompt, if job template prompts for SCM branch
      type: str
    job_type:
      description:
        - Job type applied as a prompt, if job template prompts for job type
      type: str
      choices:
        - 'run'
        - 'check'
    job_tags:
      description:
        - Job tags applied as a prompt, if job template prompts for job tags
      type: str
    skip_tags:
      description:
        - Tags to skip, applied as a prompt, if job tempalte prompts for job tags
      type: str
    limit:
      description:
        - Limit to act on, applied as a prompt, if job template prompts for limit
      type: str
    diff_mode:
      description:
        - Run diff mode, applied as a prompt, if job template prompts for diff mode
      type: bool
    verbosity:
      description:
        - Verbosity applied as a prompt, if job template prompts for verbosity
      type: str
      choices:
        - '0'
        - '1'
        - '2'
        - '3'
        - '4'
        - '5'
    workflow_job_template:
      description:
        - The workflow job template name, ID, or named URL the node exists in.
        - Used for looking up the node, cannot be modified after creation.
      required: True
      type: str
      aliases:
        - workflow
    organization:
      description:
        - The organization name, ID, or named URL of the workflow job template the node exists in.
        - Used for looking up the workflow, not a direct model field.
      type: str
    unified_job_template:
      description:
        - Name of unified job template to run in the workflow.
        - Can be a job template, project, inventory source, etc.
        - Omit if creating an approval node.
        - This parameter is mutually exclusive with C(approval_node).
      type: str
    lookup_organization:
      description:
        - Organization name, ID, or named URL the inventories, job template, project, inventory source the unified_job_template exists in.
        - If not provided, will lookup by name only, which does not work with duplicates.
      type: str
    approval_node:
      description:
        - A dictionary of Name, description, and timeout values for the approval node.
        - This parameter is mutually exclusive with C(unified_job_template).
      type: dict
      suboptions:
        name:
          description:
            - Name of this workflow approval template.
          type: str
          required: True
        description:
          description:
            - Optional description of this workflow approval template.
          type: str
        timeout:
          description:
            - The amount of time (in seconds) before the approval node expires and fails.
          type: int
    all_parents_must_converge:
      description:
        - If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node
      type: bool
    identifier:
      description:
        - An identifier for this node that is unique within its workflow.
        - It is copied to workflow job nodes corresponding to this node.
      required: True
      type: str
    always_nodes:
      description:
        - Nodes that will run after this node completes.
        - List of node identifiers.
      type: list
      elements: str
    success_nodes:
      description:
        - Nodes that will run after this node on success.
        - List of node identifiers.
      type: list
      elements: str
    failure_nodes:
      description:
        - Nodes that will run after this node on failure.
        - List of node identifiers.
      type: list
      elements: str
    credentials:
      description:
        - Credential names, IDs, or named URLs to be applied to job as launch-time prompts.
        - List of credential names, IDs, or named URLs.
        - Uniqueness is not handled rigorously.
      type: list
      elements: str
    execution_environment:
      description:
        - Execution Environment name, ID, or named URL applied as a prompt, assuming job template prompts for execution environment
      type: str
    forks:
      description:
        - Forks applied as a prompt, assuming job template prompts for forks
      type: int
    instance_groups:
      description:
        - List of Instance Group names, IDs, or named URLs applied as a prompt, assuming job template prompts for instance groups
      type: list
      elements: str
    job_slice_count:
      description:
        - Job Slice Count applied as a prompt, assuming job template prompts for job slice count
      type: int
    labels:
      description:
        - List of labels applied as a prompt, assuming job template prompts for labels
      type: list
      elements: str
    timeout:
      description:
        - Timeout applied as a prompt, assuming job template prompts for timeout
      type: int
    state:
      description:
        - Desired state of the resource.
      choices: ["present", "absent", "exists"]
      default: "present"
      type: str
extends_documentation_fragment: awx.awx.auth
'''

EXAMPLES = '''
- name: Create a node, follows workflow_job_template example
  workflow_job_template_node:
    identifier: my-first-node
    workflow: example-workflow
    unified_job_template: jt-for-node-use
    organization: Default  # organization of workflow job template
    extra_data:
      foo_key: bar_value

- name: Create parent node for prior node
  workflow_job_template_node:
    identifier: my-root-node
    workflow: example-workflow
    unified_job_template: jt-for-node-use
    organization: Default
    success_nodes:
      - my-first-node

- name: Create workflow with 2 Job Templates and an approval node in between
  block:
  - name: Create a workflow job template
    tower_workflow_job_template:
      name: my-workflow-job-template
      ask_scm_branch_on_launch: true
      organization: Default

  - name: Create 1st node
    tower_workflow_job_template_node:
      identifier: my-first-node
      workflow_job_template: my-workflow-job-template
      unified_job_template: some_job_template
      organization: Default

  - name: Create 2nd approval node
    tower_workflow_job_template_node:
      identifier: my-second-approval-node
      workflow_job_template: my-workflow-job-template
      organization: Default
      approval_node:
        description: "Do this?"
        name: my-second-approval-node
        timeout: 3600

  - name: Create 3rd node
    tower_workflow_job_template_node:
      identifier: my-third-node
      workflow_job_template: my-workflow-job-template
      unified_job_template: some_other_job_template
      organization: Default

  - name: Link 1st node to 2nd Approval node
    tower_workflow_job_template_node:
      identifier: my-first-node
      workflow_job_template: my-workflow-job-template
      organization: Default
      success_nodes:
        - my-second-approval-node

  - name: Link 2nd Approval Node 3rd node
    tower_workflow_job_template_node:
      identifier: my-second-approval-node
      workflow_job_template: my-workflow-job-template
      organization: Default
      success_nodes:
        - my-third-node
'''

from ..module_utils.controller_api import ControllerAPIModule


def main():
    # Any additional arguments that are not fields of the item can be added here
    argument_spec = dict(
        identifier=dict(required=True),
        workflow_job_template=dict(required=True, aliases=['workflow']),
        organization=dict(),
        extra_data=dict(type='dict'),
        inventory=dict(),
        scm_branch=dict(),
        job_type=dict(choices=['run', 'check']),
        job_tags=dict(),
        skip_tags=dict(),
        limit=dict(),
        diff_mode=dict(type='bool'),
        verbosity=dict(choices=['0', '1', '2', '3', '4', '5']),
        unified_job_template=dict(),
        lookup_organization=dict(),
        approval_node=dict(type='dict'),
        all_parents_must_converge=dict(type='bool'),
        success_nodes=dict(type='list', elements='str'),
        always_nodes=dict(type='list', elements='str'),
        failure_nodes=dict(type='list', elements='str'),
        credentials=dict(type='list', elements='str'),
        execution_environment=dict(type='str'),
        forks=dict(type='int'),
        instance_groups=dict(type='list', elements='str'),
        job_slice_count=dict(type='int'),
        labels=dict(type='list', elements='str'),
        timeout=dict(type='int'),
        state=dict(choices=['present', 'absent', 'exists'], default='present'),
    )
    mutually_exclusive = [("unified_job_template", "approval_node")]
    required_if = [
        ['state', 'absent', ['identifier']],
        ['state', 'present', ['identifier']],
        ['state', 'present', ['unified_job_template', 'approval_node', 'success_nodes', 'always_nodes', 'failure_nodes'], True],
    ]

    # Create a module for ourselves
    module = ControllerAPIModule(
        argument_spec=argument_spec,
        mutually_exclusive=mutually_exclusive,
        required_if=required_if,
    )

    # Extract our parameters
    identifier = module.params.get('identifier')
    state = module.params.get('state')
    approval_node = module.params.get('approval_node')
    new_fields = {}
    lookup_organization = module.params.get('lookup_organization')
    search_fields = {'identifier': identifier}

    # Attempt to look up the related items the user specified (these will fail the module if not found)
    workflow_job_template = module.params.get('workflow_job_template')
    workflow_job_template_id = None
    if workflow_job_template:
        wfjt_search_fields = {}
        organization = module.params.get('organization')
        if organization:
            organization_id = module.resolve_name_to_id('organizations', organization)
            wfjt_search_fields['organization'] = organization_id
        wfjt_data = module.get_one('workflow_job_templates', name_or_id=workflow_job_template, **{'data': wfjt_search_fields})
        if wfjt_data is None:
            module.fail_json(
                msg="The workflow {0} in organization {1} was not found on the controller instance server".format(workflow_job_template, organization)
            )
        workflow_job_template_id = wfjt_data['id']
        search_fields['workflow_job_template'] = new_fields['workflow_job_template'] = workflow_job_template_id

    # Attempt to look up an existing item based on the provided data
    existing_item = module.get_one('workflow_job_template_nodes', check_exists=(state == 'exists'), **{'data': search_fields})

    if state == 'absent':
        # If the state was absent we can let the module delete it if needed, the module will handle exiting from this
        module.delete_if_needed(existing_item)

    # Set lookup data to use
    search_fields = {}
    if lookup_organization:
        search_fields['organization'] = module.resolve_name_to_id('organizations', lookup_organization)

    unified_job_template = module.params.get('unified_job_template')
    if unified_job_template:
        new_fields['unified_job_template'] = module.get_one('unified_job_templates', name_or_id=unified_job_template, **{'data': search_fields})['id']
    inventory = module.params.get('inventory')
    if inventory:
        new_fields['inventory'] = module.resolve_name_to_id('inventories', inventory)

    # Create the data that gets sent for create and update
    for field_name in (
        'identifier',
        'extra_data',
        'scm_branch',
        'job_type',
        'job_tags',
        'skip_tags',
        'limit',
        'diff_mode',
        'verbosity',
        'all_parents_must_converge',
        'forks',
        'job_slice_count',
        'timeout',
    ):
        field_val = module.params.get(field_name)
        if field_val:
            new_fields[field_name] = field_val

    association_fields = {}
    for association in ('always_nodes', 'success_nodes', 'failure_nodes', 'credentials', 'instance_groups', 'labels'):
        name_list = module.params.get(association)
        if name_list is None:
            continue
        id_list = []
        for sub_name in name_list:
            if association in ['credentials', 'instance_groups', 'labels']:
                sub_obj = module.get_one(association, name_or_id=sub_name)
            else:
                endpoint = 'workflow_job_template_nodes'
                lookup_data = {'identifier': sub_name}
                if workflow_job_template_id:
                    lookup_data['workflow_job_template'] = workflow_job_template_id
                sub_obj = module.get_one(endpoint, **{'data': lookup_data})
            if sub_obj is None:
                module.fail_json(msg='Could not find {0} entry with name {1}'.format(association, sub_name))
            id_list.append(sub_obj['id'])
        association_fields[association] = id_list

    execution_environment = module.params.get('execution_environment')
    if execution_environment is not None:
        if execution_environment == '':
            new_fields['execution_environment'] = ''
        else:
            ee = module.get_one('execution_environments', name_or_id=execution_environment)
            if ee is None:
                module.fail_json(msg='could not find execution_environment entry with name {0}'.format(execution_environment))
            else:
                new_fields['execution_environment'] = ee['id']

    # In the case of a new object, the utils need to know it is a node
    new_fields['type'] = 'workflow_job_template_node'

    # If the state was present and we can let the module build or update the existing item, this will return on its own
    module.create_or_update_if_needed(
        existing_item,
        new_fields,
        endpoint='workflow_job_template_nodes',
        item_type='workflow_job_template_node',
        auto_exit=not approval_node,
        associations=association_fields,
    )

    # Create approval node unified template or update existing
    if approval_node:
        # Set Approval Fields
        new_fields = {}

        # Extract Parameters
        if approval_node.get('name') is None:
            module.fail_json(msg="Approval node name is required to create approval node.")
        if approval_node.get('name') is not None:
            new_fields['name'] = approval_node['name']
        if approval_node.get('description') is not None:
            new_fields['description'] = approval_node['description']
        if approval_node.get('timeout') is not None:
            new_fields['timeout'] = approval_node['timeout']

        # Find created workflow node ID
        search_fields = {'identifier': identifier}
        search_fields['workflow_job_template'] = workflow_job_template_id
        workflow_job_template_node = module.get_one('workflow_job_template_nodes', **{'data': search_fields})
        workflow_job_template_node_id = workflow_job_template_node['id']
        module.json_output['workflow_node_id'] = workflow_job_template_node_id
        existing_item = None
        # Due to not able to lookup workflow_approval_templates, find the existing item in another place
        if workflow_job_template_node['related'].get('unified_job_template') is not None:
            existing_item = module.get_endpoint(workflow_job_template_node['related']['unified_job_template'])['json']
        approval_endpoint = 'workflow_job_template_nodes/{0}/create_approval_template/'.format(workflow_job_template_node_id)
        module.create_or_update_if_needed(
            existing_item, new_fields, endpoint=approval_endpoint, item_type='workflow_job_template_approval_node', associations=association_fields
        )
    module.exit_json(**module.json_output)


if __name__ == '__main__':
    main()
