# (C) Copyright 2016 Hewlett Packard Enterprise Development LP

import json
import logging
import yaml

import monasca_setup.agent_config
import monasca_setup.detection
from monasca_setup.detection.utils import find_process_cmdline

log = logging.getLogger(__name__)


class ProcessCheck(monasca_setup.detection.Plugin):
    """Setup a process check according to the passed in JSON string or YAML config file path.

       A process can be monitored by process_names or by process_username, or by both if
       the process_config list contains both dictionary entries. Pass in the dictionary containing process_names
       when watching process by name.  Pass in the dictionary containing process_username and dimensions with
       component when watching process by username. Watching by process_username is useful for groups of processes
       that are owned by a specific user. For process monitoring by process_username the component dimension
       is required since it is used to initialize the instance name in process.yaml.

       service and component dimensions are recommended to distinguish multiple components per service.  The component
       dimensions will be defaulted to the process name when it is not input when monitoring by process_names.
       exact_match is optional and defaults to false, meaning the process name search string can be found within the process name.
       exact_match can be set to true if the process_names search string should match the process name.

       Pass in a YAML config file path:
       monasca-setup -d ProcessCheck -a "conf_file_path=/home/stack/myprocess.yaml"

       or

       Pass in a JSON string command line argument:
       Using monasca-setup, you can pass in a json string with arguments --detection_args_json, or the shortcut -json.

       Monitor by process_names:
       monasca-setup -d ProcessCheck -json \
         '{"process_config":[{"process_names":["monasca-notification","monasca-api"],"dimensions":{"service":"monitoring"}}]}'

       Specifiy one or more dictionary entries to the process_config list:
       monasca-setup -d ProcessCheck -json \
         '{"process_config":[
            {"process_names":["monasca-notification","monasca-api"],"dimensions":{"service":"monitoring"}},
            {"process_names":["elasticsearch"],"dimensions":{"service":"logging"},"exact_match":"true"},
            {"process_names":["monasca-thresh"],"dimensions":{"service":"monitoring","component":"thresh"}}]}'

       Monitor by process_username:
       monasca-setup -d ProcessCheck -json \
         '{"process_config":[{"process_username":"dbadmin","dimensions":{"service":"monitoring","component":"vertica"}}]}'

       Can specify monitoring by both process_username and process_names:
       monasca-setup -d ProcessCheck -json \
         '{"process_config":[{"process_names":["monasca-api"],"dimensions":{"service":"monitoring"}},
                             {"process_username":"mon-api","dimensions":{"service":"monitoring","component":"monasca-api"}}]}'

    """
    def __init__(self, template_dir, overwrite=False, args=None, **kwargs):
        self.process_config = []
        self.valid_process_names = []
        self.valid_usernames = []
        if 'process_config' in kwargs:
            self.process_config = kwargs['process_config']

        super(ProcessCheck, self).__init__(template_dir, overwrite, args)

    def _get_config(self):
        self.conf_file_path = None
        if self.args:
            self.conf_file_path = self.args.get('conf_file_path', None)
        if self.conf_file_path:
            self._read_config(self.conf_file_path)

    def _read_config(self, config_file):
        log.info("\tUsing parameters from config file: {}".format(config_file))
        with open(config_file) as data_file:
            try:
                data = yaml.safe_load(data_file)
                if 'process_config' in data:
                    self.process_config = data['process_config']
                else:
                    log.error("\tInvalid format yaml file, missing key: process_config")
            except yaml.YAMLError as e:
                exception_msg = ("Could not read config file. Invalid yaml format detected. {0}.".format(e))
                raise Exception(exception_msg)

    def _detect(self):
        """Run detection, set self.available True if the service is detected.

        """
        self._get_config()

        for process_item in self.process_config:
            if 'dimensions' not in process_item:
                process_item['dimensions'] = {}
            if 'process_names' in process_item:
                found_process_names = []
                not_found_process_names = []
                for process_name in process_item['process_names']:
                    if find_process_cmdline(process_name) is not None:
                        found_process_names.append(process_name)
                    else:
                        not_found_process_names.append(process_name)

                # monitoring by process_names
                if not_found_process_names:
                    log.info("\tDid not discover process_name(s): {0}.".format(",".join(not_found_process_names)))
                if found_process_names:
                    process_item['found_process_names'] = found_process_names
                    if 'exact_match' in process_item:
                        if isinstance(process_item['exact_match'], basestring):
                            process_item['exact_match'] = (process_item['exact_match'].lower() == 'true')
                    else:
                        process_item['exact_match'] = False
                    self.valid_process_names.append(process_item)

            if 'process_username' in process_item:
                if 'component' in process_item['dimensions']:
                    self.valid_usernames.append(process_item)
                else:
                    log.error("\tMissing required component dimension, when monitoring by "
                              "process_username: {}".format(process_item['process_username']))

        if self.valid_process_names or self.valid_usernames:
            self.available = True

    def _monitor_by_process_name(self, process_name, exact_match=False, detailed=True, dimensions=None):
        config = monasca_setup.agent_config.Plugins()
        instance = {'name': process_name,
                    'detailed': detailed,
                    'exact_match': exact_match,
                    'search_string': [process_name],
                    'dimensions': {}}
        # default component to process name if not given
        if dimensions:
            instance['dimensions'].update(dimensions)
            if 'component' not in dimensions:
                instance['dimensions']['component'] = process_name
        else:
            instance['dimensions']['component'] = process_name
        config['process'] = {'init_config': None, 'instances': [instance]}
        return config

    def _monitor_by_process_username(self, process_username, detailed=True, dimensions=None):
        config = monasca_setup.agent_config.Plugins()
        instance = {'name': dimensions['component'],
                    'detailed': detailed,
                    'username': process_username,
                    'dimensions': {}}
        if dimensions:
            instance['dimensions'].update(dimensions)
        config['process'] = {'init_config': None, 'instances': [instance]}
        return config

    def build_config(self):
        """Build the config as a Plugins object and return.

        """
        config = monasca_setup.agent_config.Plugins()

        # Watch by process_names
        for process in self.valid_process_names:
            log.info("\tMonitoring by process_name(s): {0} "
                     "for dimensions: {1}.".format(",".join(process['found_process_names']),
                                                   json.dumps(process['dimensions'])))
            for process_name in process['found_process_names']:
                config.merge(self._monitor_by_process_name(process_name=process_name,
                                                           dimensions=process['dimensions'],
                                                           exact_match=process['exact_match']))

        # Watch by process_username
        for process in self.valid_usernames:
            log.info("\tMonitoring by process_username: {0} "
                     "for dimensions: {1}.".format(process['process_username'], json.dumps(process['dimensions'])))
            config.merge(self._monitor_by_process_username(process_username=process['process_username'],
                                                           dimensions=process['dimensions']))
        return config

    def dependencies_installed(self):
        """Return True if dependencies are installed.

        """
        return True
