# (c) 2018, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

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

DOCUMENTATION = """
---
module: cli
author: Peter Sprygada (@privateip)
short_description: Runs the specific command and returns the output
description:
  - The command specified in C(command) will be executed on the remote
    device and its output will be returned to the module.  This module
    requires that the device is supported using the C(network_cli)
    connection plugin and has a valid C(cliconf) plugin to work correctly.
version_added: "2.5"
options:
  command:
    description:
      - The command to be executed on the remote node.  The value for this
        argument will be passed unchanged to the network device and the
        output returned.
    required: yes
    default: null
  parser:
    description:
      - The parser file to pass the output from the command through to
        generate Ansible facts.  If this argument is specified, the output
        from the command will be parsed based on the rules in the
        specified parser.
    default: null
  engine:
    description:
      - Defines the engine to use when parsing the output.  This argument
        accepts one of two valid values, C(command_parser) or C(textfsm_parser).
    default: command_parser
    choices:
      - command_parser
      - textfsm_parser
  name:
    description:
      - The C(name) argument is used to define the top-level fact name to
        hold the output of textfsm_engine parser. If this argument is not provided,
        the output from parsing will not be exported. Note that this argument is
        only considered when C(engine) is C(textfsm_parser).
    default: null
"""

EXAMPLES = """
- name: return show version
  cli:
    command: show version

- name: return parsed command output
  cli:
    command: show version
    parser: parser_templates/show_version.yaml

- name: parse with textfsm_parser engine
  cli:
    command: show version
    parser: parser_templates/show_version
    engine: textfsm_parser
    name: system_facts
"""

RETURN = """
stdout:
  description: returns the output from the command
  returned: always
  type: dict
json:
  description: the output converted from json to a hash
  returned: always
  type: dict
"""

import json

from ansible.plugins.action import ActionBase
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.module_utils._text import to_text
from ansible.errors import AnsibleError
from ansible.utils.display import Display

display = Display()


class ActionModule(ActionBase):

    def run(self, tmp=None, task_vars=None):
        ''' handler for cli operations '''

        if task_vars is None:
            task_vars = dict()

        result = super(ActionModule, self).run(tmp, task_vars)
        del tmp  # tmp no longer has any effect

        try:
            command = self._task.args['command']
            parser = self._task.args.get('parser')
            engine = self._task.args.get('engine', 'command_parser')
            if engine == 'textfsm_parser':
                name = self._task.args.get('name')
            elif engine == 'command_parser' and self._task.args.get('name'):
                display.warning('name is unnecessary when using command_parser and will be ignored')
                del self._task.args['name']
        except KeyError as exc:
            raise AnsibleError(to_text(exc))

        socket_path = getattr(self._connection, 'socket_path') or task_vars.get('ansible_socket')
        connection = Connection(socket_path)

        # make command a required argument
        if not command:
            raise AnsibleError('missing required argument `command`')

        try:
            output = connection.get(command)
        except ConnectionError as exc:
            raise AnsibleError(to_text(exc))

        result['stdout'] = output

        # try to convert the cli output to native json
        try:
            json_data = json.loads(output)
        except Exception:
            json_data = None

        result['json'] = json_data

        if parser:
            if engine not in ('command_parser', 'textfsm_parser'):
                raise AnsibleError('missing or invalid value for argument engine')

            new_task = self._task.copy()
            new_task.args = {
                'file': parser,
                'content': (json_data or output)
            }
            if engine == 'textfsm_parser':
                new_task.args.update({'name': name})

            kwargs = {
                'task': new_task,
                'connection': self._connection,
                'play_context': self._play_context,
                'loader': self._loader,
                'templar': self._templar,
                'shared_loader_obj': self._shared_loader_obj
            }

            task_parser = self._shared_loader_obj.action_loader.get(engine, **kwargs)
            result.update(task_parser.run(task_vars=task_vars))

        self._remove_tmp_path(self._connection._shell.tmpdir)

        # this is needed so the strategy plugin can identify the connection as
        # a persistent connection and track it, otherwise the connection will
        # not be closed at the end of the play
        socket_path = getattr(self._connection, 'socket_path') or task_vars.get('ansible_socket')
        self._task.args['_ansible_socket'] = socket_path

        return result
