# Copyright 2017 FUJITSU LIMITED
#
# 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.

import logging

from oslo_utils import importutils

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

LOG = logging.getLogger(__name__)


class InfluxDBRelay(detection.Plugin):
    """Detects influxdb-relay and sets up its monitoring

    Monitored items:

    * process
    * http_check

    """

    PROC_NAME = 'influxdb-relay'
    """Name of the InfluxDB Relay process expected to be found in the system"""
    DEFAULTS = {
        'bind_address': '127.0.0.1',
        'bind_port': 9096
    }
    RELAY_NODE_ARG_NAME = 'influxdb_relay_node'

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

        proc = utils.find_process_name(self.PROC_NAME)
        process_found = proc is not None

        config_file = self._get_config_file(proc) if process_found else None
        config_file_found = config_file is not None

        dependencies_installed = self.dependencies_installed()

        self.available = (process_found and config_file_found and
                          dependencies_installed)

        if not self.available:
            err_chunks = []
            if not process_found:
                err_chunks.append('\tinfluxdb-relay plugin cannot locate '
                                  '"%s" process.' % self.PROC_NAME)
            elif not config_file_found:
                err_chunks.append('\tinfluxdb-relay plugin cannot locate '
                                  'configuration file.')
            elif not dependencies_installed:
                err_chunks.append('\tinfluxdb-relay plugin requires "toml" '
                                  'to be installed')
            LOG.warning('Plugin for influxdb-relay will not be configured.\n'
                        'Following issue have to be resolved: %s' %
                        '\n'.join(err_chunks))
        else:
            self._config = self._load_config(config_file)

    def build_config(self):
        """Build the config as a Plugins object and return."""
        LOG.info("\tEnabling the influxdb-relay check")
        config = agent_config.Plugins()

        config.merge(self._monitor_process())
        config.merge(self._monitor_endpoint())

        return config

    def _monitor_process(self):
        LOG.info("\tMonitoring the influxdb-relay process")

        dimensions = {}
        if self.args and self.args.get(self.RELAY_NODE_ARG_NAME):
            dimensions.update({self.RELAY_NODE_ARG_NAME: self.args.get(self.RELAY_NODE_ARG_NAME)})

        return detection.watch_process([self.PROC_NAME],
                                       service='influxdb',
                                       component='influxdb-relay',
                                       exact_match=False,
                                       dimensions=dimensions)

    def _monitor_endpoint(self):
        config = agent_config.Plugins()
        http_conf = self._config.get('http', None)
        if isinstance(http_conf, list):
            http_conf = http_conf[0]

        if http_conf:
            host, port = self._explode_bind_address(http_conf)
            listening = utils.find_addrs_listening_on_port(port)
            if listening:
                LOG.info("\tMonitoring the influxdb-relay ping endpoint")

                dimensions = {'service': 'influxdb',
                              'component': 'influxdb-relay'}
                if self.args and self.args.get(self.RELAY_NODE_ARG_NAME):
                    dimensions.update(
                        {self.RELAY_NODE_ARG_NAME: self.args.get(self.RELAY_NODE_ARG_NAME)})

                instance = {
                    'name': 'influxdb-relay',
                    'url': 'http://%s:%d/ping' % (host, port),
                    'dimensions': dimensions
                }

                config['http_check'] = {
                    'init_config': None,
                    'instances': [instance]
                }
            else:
                LOG.warning('\tinfluxdb-relay[http] is enabled but nothing '
                            'could be found listening at %d port. '
                            'It might have happened that process '
                            'was just killed and hence port %d '
                            'was released.', port, port)

        return config

    def dependencies_installed(self):
        return importutils.try_import('toml', False)

    @staticmethod
    def _explode_bind_address(http_conf):
        bind_address = http_conf['bind-addr']
        path, port = bind_address.split(':')

        if not path:
            path = InfluxDBRelay.DEFAULTS['bind_address']
        if not port:
            port = InfluxDBRelay.DEFAULTS['bind_port']

        return path, int(port)

    @staticmethod
    def _load_config(config_file):
        """Loads toml configuration from specified path.

        Method loads configuration from specified `path`
        and parses it with :py:class:`configparser.RawConfigParser`

        """
        try:
            return importutils.import_module('toml').load(config_file)
        except Exception as ex:
            LOG.error('Failed to parse %s', config_file)
            LOG.exception(ex)

    @staticmethod
    def _get_config_file(proc):
        """Tries to retrieve config file location.

        influxdb-relay is launched with ```-config```
        flag in cmdline. That fact is used by this method.
        If, by any mean, that switch is not part of
        :py:method:`psutil.Process.cmdline`, method simply
        fallbacks to None

        :param proc: current process, :py:class:`psutil.Process`
        :type proc: :py:class:`psutil.Process`
        :return: config file path or None
        :rtype: str or None

        """
        cmdline = proc.as_dict(attrs=['cmdline'])['cmdline']
        config_flag = '-config'
        if config_flag in cmdline:
            pos = cmdline.index(config_flag)
            return cmdline[pos + 1]
        LOG.warning('%s switch was not found in influxdb-relay cmdline',
                    config_flag)
        return None
