# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__metaclass__ = type

import datetime
import json
import time
import os

from functools import partial
from functools import reduce

from ansible.parsing.ajson import AnsibleJSONEncoder
from ansible.plugins.callback import CallbackBase

DOCUMENTATION = '''
    callback: json
    short_description: Log Ansible results on filesystem
    version_added: "1.0"
    description:
        - This callback converts all events into a JSON file
          stored in the selected validations logging directory,
          as defined by the $VALIDATIONS_LOG_DIR env variable,
          or the $HOME/validations by default.
    type: aggregate
    requirements: None
'''

VALIDATIONS_LOG_DIR = os.environ.get(
    'VALIDATIONS_LOG_DIR',
    os.path.expanduser('~/validations'))


def current_time():
    return '%sZ' % datetime.datetime.utcnow().isoformat()


def secondsToStr(t):
    def rediv(ll, b):
        return list(divmod(ll[0], b)) + ll[1:]

    return "%d:%02d:%02d.%03d" % tuple(
        reduce(rediv, [[
            t * 1000,
        ], 1000, 60, 60]))


class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'aggregate'
    CALLBACK_NAME = 'validation_json'
    CALLBACK_NEEDS_WHITELIST = True

    def __init__(self, display=None):
        super(CallbackModule, self).__init__(display)
        self.results = []
        self.simple_results = []
        self.env = {}
        self.start_time = None
        self.current_time = current_time()

    def _new_play(self, play):
        return {
            'play': {
                'host': play.get_name(),
                'validation_id': self.env['playbook_name'],
                'validation_path': self.env['playbook_path'],
                'id': (os.getenv('ANSIBLE_UUID') if os.getenv('ANSIBLE_UUID')
                       else str(play._uuid)),
                'duration': {
                    'start': current_time()
                }
            },
            'tasks': []
        }

    def _new_task(self, task):
        return {
            'task': {
                'name': task.get_name(),
                'id': str(task._uuid),
                'duration': {
                    'start': current_time()
                }
            },
            'hosts': {}
        }

    def _val_task(self, task_name):
        return {
            'task': {
                'name': task_name,
                'hosts': {}
            }
        }

    def _val_task_host(self, task_name):
        return {
            'task': {
                'name': task_name,
                'hosts': {}
            }
        }

    def v2_playbook_on_start(self, playbook):
        self.start_time = time.time()
        pl = playbook._file_name
        validation_id = os.path.splitext(os.path.basename(pl))[0]
        self.env = {
            "playbook_name": validation_id,
            "playbook_path": playbook._basedir
        }

    def v2_playbook_on_play_start(self, play):
        self.results.append(self._new_play(play))

    def v2_playbook_on_task_start(self, task, is_conditional):
        self.results[-1]['tasks'].append(self._new_task(task))

    def v2_playbook_on_handler_task_start(self, task):
        self.results[-1]['tasks'].append(self._new_task(task))

    def v2_playbook_on_stats(self, stats):
        """Display info about playbook statistics"""

        hosts = sorted(stats.processed.keys())

        summary = {}
        for h in hosts:
            s = stats.summarize(h)
            summary[h] = s

        output = {
            'plays': self.results,
            'stats': summary,
            'validation_output': self.simple_results
        }

        log_file = "{}/{}_{}_{}.json".format(
            VALIDATIONS_LOG_DIR,
            (os.getenv('ANSIBLE_UUID') if os.getenv('ANSIBLE_UUID') else
             self.results[0].get('play').get('id')),
            self.env['playbook_name'],
            self.current_time)

        with open(log_file, 'w') as js:
            js.write(json.dumps(output,
                                cls=AnsibleJSONEncoder,
                                indent=4,
                                sort_keys=True))

    def _record_task_result(self, on_info, result, **kwargs):
        """This function is used as a partial to add info in a single method
        """
        host = result._host
        task = result._task
        task_result = result._result.copy()
        task_result.update(on_info)
        task_result['action'] = task.action
        self.results[-1]['tasks'][-1]['hosts'][host.name] = task_result

        if 'failed' in task_result.keys():
            self.simple_results.append(self._val_task(task.name))
            self.simple_results[-1]['task']['status'] = "FAILED"
            self.simple_results[-1]['task']['hosts'][host.name] = task_result
        if 'warnings' in task_result.keys() and task_result.get('warnings'):
            self.simple_results.append(self._val_task(task.name))
            self.simple_results[-1]['task']['status'] = "WARNING"
            self.simple_results[-1]['task']['hosts'][host.name] = task_result

        end_time = current_time()
        time_elapsed = secondsToStr(time.time() - self.start_time)
        for result in self.results:
            if len(result['tasks']) > 1:
                result['tasks'][-1]['task']['duration']['end'] = end_time
            result['play']['duration']['end'] = end_time
            result['play']['duration']['time_elapsed'] = time_elapsed

    def v2_playbook_on_no_hosts_matched(self):
        no_match_result = self._val_task('No tasks run')
        no_match_result['task']['status'] = "FAILED"
        no_match_result['task']['info'] = (
            "None of the hosts specified"
            " were matched in the inventory file")

        output = {
            'plays': self.results,
            'stats': {
                'No host matched': {
                    'changed': 0,
                    'failures': 1,
                    'ignored': 0,
                    'ok': 0,
                    'rescued': 0,
                    'skipped': 0,
                    'unreachable': 0}},
            'validation_output': self.simple_results + [no_match_result]
        }

        log_file = "{}/{}_{}_{}.json".format(
            VALIDATIONS_LOG_DIR,
            os.getenv(
                'ANSIBLE_UUID',
                self.results[0].get('play').get('id')),
            self.env['playbook_name'],
            self.current_time)

        with open(log_file, 'w') as js:
            js.write(json.dumps(output,
                                cls=AnsibleJSONEncoder,
                                indent=4,
                                sort_keys=True))

    def __getattribute__(self, name):
        """Return ``_record_task_result`` partial with a dict
           containing skipped/failed if necessary
        """
        if name not in ('v2_runner_on_ok', 'v2_runner_on_failed',
                        'v2_runner_on_unreachable', 'v2_runner_on_skipped'):
            return object.__getattribute__(self, name)

        on = name.rsplit('_', 1)[1]

        on_info = {}
        on_info[on] = True

        return partial(self._record_task_result, on_info)