# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
# 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.

from collections import defaultdict
from glob import glob
import os
import time

try:
    from xml.etree.ElementTree import ElementTree
except ImportError:
    try:
        from elementtree import ElementTree
    except ImportError:
        pass

from monasca_agent.collector.checks import AgentCheck
from monasca_agent.common.util import get_hostname


class Skip(Exception):

    """Raised by :class:`Jenkins` when it comes across

    a build or job that should be excluded from being checked.
    """

    def __init__(self, reason, dir_name):
        message = 'skipping build or job at %s because %s' % (dir_name, reason)
        Exception.__init__(self, message)


class Jenkins(AgentCheck):
    datetime_format = '%Y-%m-%d_%H-%M-%S'

    def __init__(self, name, init_config, agent_config):
        AgentCheck.__init__(self, name, init_config, agent_config)
        self.high_watermarks = {}

    def _extract_timestamp(self, dir_name):
        if not os.path.isdir(dir_name):
            raise Skip('its not a build directory', dir_name)

        try:
            # Parse the timestamp from the directory name
            date_str = os.path.basename(dir_name)
            time_tuple = time.strptime(date_str, self.datetime_format)
            return time.mktime(time_tuple)
        except ValueError:
            raise Exception("Error with build directory name, not a parsable date: %s" % (dir_name))

    def _get_build_metadata(self, dir_name):
        if os.path.exists(os.path.join(dir_name, 'jenkins_build.tar.gz')):
            raise Skip('the build has already been archived', dir_name)

        # Read the build.xml metadata file that Jenkins generates
        build_metadata = os.path.join(dir_name, 'build.xml')

        if not os.access(build_metadata, os.R_OK):
            self.log.debug("Can't read build file at %s" % (build_metadata))
            raise Exception("Can't access build.xml at %s" % (build_metadata))
        else:
            tree = ElementTree()
            tree.parse(build_metadata)

            keys = ['result', 'number', 'duration']

            kv_pairs = ((k, tree.find(k)) for k in keys)
            d = dict([(k, v.text) for k, v in kv_pairs if v is not None])

            try:
                d['branch'] = tree.find('actions') \
                    .find('hudson.plugins.git.util.BuildData') \
                    .find('buildsByBranchName') \
                    .find('entry') \
                    .find('hudson.plugins.git.util.Build') \
                    .find('revision') \
                    .find('branches') \
                    .find('hudson.plugins.git.Branch') \
                    .find('name') \
                    .text
            except Exception:  # nosec
                pass
            return d

    def _get_build_results(self, instance_key, job_dir):
        job_name = os.path.basename(job_dir)

        try:
            dirs = glob(os.path.join(job_dir, 'builds', '*_*'))
            if len(dirs) > 0:
                dirs = sorted(dirs, reverse=True)
                # We try to get the last valid build
                for index in range(0, len(dirs) - 1):
                    dir_name = dirs[index]
                    try:
                        timestamp = self._extract_timestamp(dir_name)
                    except Skip:
                        continue

                    # Check if it's a new build
                    if timestamp > self.high_watermarks[instance_key][job_name]:
                        # If we can't get build metadata, we try the previous one
                        try:
                            build_metadata = self._get_build_metadata(dir_name)
                        except Exception:  # nosec
                            continue

                        output = {
                            'job_name': job_name,
                            'timestamp': timestamp,
                            'event_type': 'build result'
                        }
                        output.update(build_metadata)
                        self.high_watermarks[instance_key][job_name] = timestamp
                        yield output
                    # If it not a new build, stop here
                    else:
                        break
        except Exception as e:
            self.log.error("Error while working on job %s, exception: %s" % (job_name, e))

    def check(self, instance):
        if self.high_watermarks.get(instance.get('name'), None) is None:
            # On the first run of check(), prime the high_watermarks dict
            # (Setting high_watermarks in the next statement prevents
            # any kind of infinite loop (assuming nothing ever sets
            # high_watermarks to None again!))
            self.high_watermarks[instance.get('name')] = defaultdict(lambda: 0)
            self.check(instance)

        jenkins_home = instance.get('jenkins_home', None)

        if not jenkins_home:
            raise Exception("No jenkins_home directory set in the config file")

        jenkins_jobs_dir = os.path.join(jenkins_home, 'jobs', '*')
        job_dirs = glob(jenkins_jobs_dir)

        if not job_dirs:
            raise Exception('No jobs found in `%s`! '
                            'Check `jenkins_home` in your config' % (jenkins_jobs_dir))

        for job_dir in job_dirs:
            for output in self._get_build_results(instance.get('name'), job_dir):
                output['host'] = get_hostname()
