# (c) 2018 Red Hat Inc.
# 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

import json
import time

from ansible.module_utils._text import to_text
from ansible.module_utils.connection import ConnectionError
from ansible.module_utils.network.common.utils import to_list
from ansible.plugins.httpapi import HttpApiBase

try:
    from __main__ import display
except ImportError:
    from ansible.utils.display import Display
    display = Display()


class HttpApi(HttpApiBase):
    def send_request(self, data, **message_kwargs):
        data = to_list(data)
        if self._become:
            display.vvvv('firing event: on_become')
            data.insert(0, {"cmd": "enable", "input": self._become_pass})

        output = message_kwargs.get('output', 'text')
        request = request_builder(data, output)
        headers = {'Content-Type': 'application/json-rpc'}

        response, response_data = self.connection.send('/command-api', request, headers=headers, method='POST')

        try:
            response_data = json.loads(to_text(response_data.getvalue()))
        except ValueError:
            raise ConnectionError('Response was not valid JSON, got {0}'.format(
                to_text(response_data.getvalue())
            ))

        results = handle_response(response_data)

        if self._become:
            results = results[1:]
        if len(results) == 1:
            results = results[0]

        return results

    def get_prompt(self):
        # Fake a prompt for @enable_mode
        if self._become:
            return '#'
        return '>'

    # Imported from module_utils
    def edit_config(self, config, commit=False, replace=False):
        """Loads the configuration onto the remote devices

        If the device doesn't support configuration sessions, this will
        fallback to using configure() to load the commands.  If that happens,
        there will be no returned diff or session values
        """
        session = 'ansible_%s' % int(time.time())
        result = {'session': session}
        banner_cmd = None
        banner_input = []

        commands = ['configure session %s' % session]
        if replace:
            commands.append('rollback clean-config')

        for command in config:
            if command.startswith('banner'):
                banner_cmd = command
                banner_input = []
            elif banner_cmd:
                if command == 'EOF':
                    command = {'cmd': banner_cmd, 'input': '\n'.join(banner_input)}
                    banner_cmd = None
                    commands.append(command)
                else:
                    banner_input.append(command)
                    continue
            else:
                commands.append(command)

        try:
            response = self.send_request(commands)
        except Exception:
            commands = ['configure session %s' % session, 'abort']
            response = self.send_request(commands, output='text')
            raise

        commands = ['configure session %s' % session, 'show session-config diffs']
        if commit:
            commands.append('commit')
        else:
            commands.append('abort')

        response = self.send_request(commands, output='text')
        diff = response[1].strip()
        if diff:
            result['diff'] = diff

        return result

    def run_commands(self, commands, check_rc=True):
        """Runs list of commands on remote device and returns results
        """
        output = None
        queue = list()
        responses = list()

        def run_queue(queue, output):
            try:
                response = to_list(self.send_request(queue, output=output))
            except Exception as exc:
                if check_rc:
                    raise
                return to_text(exc)

            if output == 'json':
                response = [json.loads(item) for item in response]
            return response

        for item in to_list(commands):
            cmd_output = 'text'
            if isinstance(item, dict):
                command = item['command']
                if 'output' in item:
                    cmd_output = item['output']
            else:
                command = item

            # Emulate '| json' from CLI
            if command.endswith('| json'):
                command = command.rsplit('|', 1)[0]
                cmd_output = 'json'

            if output and output != cmd_output:
                responses.extend(run_queue(queue, output))
                queue = list()

            output = cmd_output
            queue.append(command)

        if queue:
            responses.extend(run_queue(queue, output))

        return responses

    def load_config(self, config, commit=False, replace=False):
        """Loads the configuration onto the remote devices

        If the device doesn't support configuration sessions, this will
        fallback to using configure() to load the commands.  If that happens,
        there will be no returned diff or session values
        """
        return self.edit_config(config, commit, replace)


def handle_response(response):
    if 'error' in response:
        error = response['error']

        error_text = []
        for data in error['data']:
            error_text.extend(data.get('errors', []))
        error_text = '\n'.join(error_text) or error['message']

        raise ConnectionError(error_text, code=error['code'])

    results = []
    for result in response['result']:
        if 'messages' in result:
            results.append(result['messages'][0])
        elif 'output' in result:
            results.append(result['output'].strip())
        else:
            results.append(json.dumps(result))

    return results


def request_builder(commands, output, reqid=None):
    params = dict(version=1, cmds=commands, format=output)
    return json.dumps(dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params))
