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

import collections
import time

from ansible import constants as C
from ansible.plugins.callback import CallbackBase
from datetime import datetime
from datetime import timedelta

DOCUMENTATION = '''
    callback: tripleo_profile_tasks
    type: aggregate
    short_description: adds time information to tasks
    version_added: "2.9"
    description:
      - Based on upstream profile_tasks but formatted for tripleo_dense
    requirements:
      - whitelisting in configuration - see examples section below for details.
    options:
      output_limit:
        description: Number of tasks to display in the summary
        default: 20
        env:
          - name: PROFILE_TASKS_TASK_OUTPUT_LIMIT
        ini:
          - section: callback_profile_tasks
            key: task_output_limit
      sort_order:
        description: Adjust the sorting output of summary tasks
        choices: ['descending', 'ascending', 'none']
        default: 'descending'
        env:
          - name: PROFILE_TASKS_SORT_ORDER
        ini:
          - section: callback_profile_tasks
            key: sort_order
'''


class CallbackModule(CallbackBase):

    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'aggregate'
    CALLBACK_NAME = 'tripleo_profile_tasks'
    CALLBACK_NEEDS_WHITELIST = True

    def __init__(self):
        self.stats = collections.OrderedDict()
        self.tasks = {}
        self.current = None
        self.sort_order = None
        self.task_output_limit = None
        self.start_time = time.time()
        super(CallbackModule, self).__init__()

    def set_options(self, task_keys=None, var_options=None, direct=None):
        super(CallbackModule, self).set_options(task_keys=task_keys,
                                                var_options=var_options,
                                                direct=direct)

        self.sort_order = self.get_option('sort_order')
        if self.sort_order is not None:
            if self.sort_order == 'ascending':
                self.sort_order = False
            elif self.sort_order == 'descending':
                self.sort_order = True
            elif self.sort_order == 'none':
                self.sort_order = None

        self.task_output_limit = self.get_option('output_limit')
        if self.task_output_limit is not None:
            if self.task_output_limit == 'all':
                self.task_output_limit = None
            else:
                self.task_output_limit = int(self.task_output_limit)

    def _get_uuid(self, item=None):
        uuid = ''

        if item and getattr(item, '_uuid', False):
            # item is a task
            uuid = item._uuid
        elif item and getattr(item, '_task', False):
            # item is a result (may not have a _task tho)
            if getattr(item._task, '_uuid', False):
                uuid = item._task._uuid
        return '{:36}'.format(uuid)

    def _get_host(self, result):
        delegated_vars = result._result.get('_ansible_delegated_vars', None)
        if (getattr(result, '_host', False)
                and getattr(result._host, 'get_name', False)):
            msg = '%s' % result._host.get_name()
        elif (getattr(result, '_host', False)
                and getattr(result._host, 'name', False)):
            msg = '%s' % result._host.name
        else:
            msg = 'UNKNOWN'
        if delegated_vars:
            msg += ' -> %s' % delegated_vars['ansible_host']
        return msg

    def _output(self, msg, color=None):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
        if isinstance(msg, list):
            output = ' | '.join([timestamp] + msg)
        else:
            output = timestamp + ' | ' + msg
        self._display.display(output, color=color)

    def _start_task(self, task, host=None):
        hostname = None
        if host:
            hostname = host.name
        k = (hostname, task._uuid)
        self.stats[k] = {'start': time.time(),
                         'total_time': 0.0}
        self.tasks[task._uuid] = task.get_name()

    def _end_task(self, result):
        uuid = self._get_uuid(result)
        host = self._get_host(result)
        k = (host, uuid)
        # the task never started, insert shrug emoji here.
        if k not in self.stats:
            self._display.warning('{} missing from stats'.format(k))
            return
        total_time = time.time() - self.stats[k]['start']
        self.stats[k]['total_time'] = total_time

        line = [
            uuid,
            u'{:>10}'.format('TIMING'),
            self.tasks.get(uuid, ''),
            host,
            str(timedelta(seconds=time.time() - self.start_time)),
            u'{0:.02f}s'.format(total_time)
        ]
        self._output(line, C.COLOR_DEBUG)

    def v2_runner_on_start(self, host, task):
        self._start_task(task, host)

    # task ends
    def v2_playbook_on_failed(self, result, ignore_errors=False):
        self._end_task(result)

    def v2_runner_on_ok(self, result):
        self._end_task(result)

    def v2_runner_item_on_ok(self, result):
        self._end_task(result)

    def v2_runner_on_failed(self, result, ignore_errors=False):
        self._end_task(result)

    def v2_runner_item_on_failed(self, result):
        self._end_task(result)

    def v2_runner_on_skipped(self, result):
        self._end_task(result)

    def v2_runner_on_unreachable(self, result):
        self._end_task(result)

    # playbook finished
    def playbook_on_stats(self, stats):
        self.current = None
        results = self.stats.items()
        # Sort the tasks by the specified sort
        if self.sort_order is not None:
            results = sorted(
                self.stats.items(),
                key=lambda x: x[1]['total_time'],
                reverse=self.sort_order,
            )
        results = results[:self.task_output_limit]

        self._output('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
                     ' Summary Information '
                     '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
        self._output(
            '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
            ' Total Tasks: {:<10} '
            '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'.format(len(self.tasks)))

        self._output(
            '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
            ' Elapsed Time: {} '
            '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'.format(
                str(timedelta(seconds=(time.time() - self.start_time)))))

        header = [
            '{:>36}'.format('UUID'),
            '{:>10}'.format('Info'),
            '{:>10}'.format('Host'),
            '{:>11}'.format('Task Name'),
            '{:>10}'.format('Run Time'),
        ]
        self._output(' | '.join(header))

        for (host, uuid), result in results:
            line = [
                uuid,
                u'{:>10}'.format('SUMMARY'),
                u'{:>10}'.format(host),
                self.tasks.get(uuid, ''),
                u'{0:.02f}s'.format(result['total_time'])
            ]
            self._output(' | '.join(line))

        self._output('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
                     ' End Summary Information '
                     '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
