#!/usr/bin/env python
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP

""" Detect running daemons then configure and start the agent.
"""

import argparse
from glob import glob
import json
import logging
import os
import pwd
import socket
import subprocess
import sys

import agent_config
import monasca_setup.utils as utils
from monasca_setup.utils import write_template
from service.detection import detect_init

log = logging.getLogger(__name__)

CUSTOM_PLUGIN_PATH = '/usr/lib/monasca/agent/custom_detect.d'
# dirname is called twice to get the dir 1 above the location of the script
PREFIX_DIR = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))


def main(argv=None):
    parser = argparse.ArgumentParser(description='Configure and setup the agent. In a full run it will detect running' +
                                                 ' daemons then configure and start the agent.',
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    args = parse_arguments(parser)

    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")
    else:
        logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

    if args.dry_run:
        log.info("Running in dry run mode, no changes will be made only reported")

    # Detect and if possibly enable the agent service
    agent_service = detect_init(PREFIX_DIR, args.config_dir, args.log_dir, args.template_dir, username=args.user)

    # Skip base setup if only installing plugins or running specific detection
    # plugins
    if not args.install_plugins_only and args.detection_plugins is None:
        if not args.skip_enable:
            agent_service.enable()

        # Verify required options
        if args.username is None or args.password is None or args.keystone_url is None:
            log.error('Username, password and keystone_url are required when running full configuration.')
            parser.print_help()
            sys.exit(1)
        base_configuration(args)

    # Collect the set of detection plugins to run
    detected_plugins = utils.discover_plugins(CUSTOM_PLUGIN_PATH)
    if args.system_only:
        from detection.plugins.system import System
        plugins = [System]
    elif args.detection_plugins is not None:
        plugins = utils.select_plugins(args.detection_plugins, detected_plugins)
    elif args.skip_detection_plugins is not None:
        plugins = utils.select_plugins(args.skip_detection_plugins, detected_plugins, skip=True)
    else:
        plugins = detected_plugins
    plugin_names = [p.__name__ for p in plugins]

    if args.remove:  # Remove entries for each plugin from the various plugin config files
        changes = remove_config(args, plugin_names)
    else:
        # Run detection for all the plugins, halting on any failures if plugins were specified in the arguments
        detected_config = plugin_detection(plugins, args.template_dir, args.detection_args,
                                           args.detection_args_json,
                                           skip_failed=(args.detection_plugins is None))
        if detected_config is None:
            return 1  # Indicates detection problem, skip remaining steps and give non-zero exit code

        changes = modify_config(args, detected_config)

    # Don't restart if only doing detection plugins and no changes found
    if args.detection_plugins is not None and not changes:
        log.info('No changes found for plugins {0}, skipping restart of Monasca Agent'.format(plugin_names))
        return 0
    elif args.dry_run:
        log.info('Running in dry mode, skipping changes and restart of Monasca Agent')
        return 0

    # Now that the config is built, start the service
    if args.install_plugins_only:
        log.info('Command line option install_plugins_only set, skipping '
                 'service (re)start.')
    else:
        try:
            agent_service.start(restart=True)
        except subprocess.CalledProcessError:
            log.error('The service did not startup correctly see %s' % args.log_dir)


def base_configuration(args):
    """Write out the primary Agent configuration and setup the service.
    :param args: Arguments from the command line
    :return: None
    """
    stat = pwd.getpwnam(args.user)

    uid = stat.pw_uid
    gid = stat.pw_gid

    # Write the main agent.yaml - Note this is always overwritten
    log.info('Configuring base Agent settings.')
    dimensions = {}
    # Join service in with the dimensions
    if args.service:
        dimensions.update({'service': args.service})
    if args.dimensions:
        dimensions.update(dict(item.strip().split(":") for item in args.dimensions.split(",")))

    args.dimensions = dict((name, value) for (name, value) in dimensions.iteritems())
    write_template(os.path.join(args.template_dir, 'agent.yaml.template'),
                   os.path.join(args.config_dir, 'agent.yaml'),
                   {'args': args, 'hostname': socket.getfqdn()},
                   group=gid,
                   user=uid,
                   is_yaml=True)

    # Write the supervisor.conf
    write_template(os.path.join(args.template_dir, 'supervisor.conf.template'),
                   os.path.join(args.config_dir, 'supervisor.conf'),
                   {'prefix': PREFIX_DIR, 'log_dir': args.log_dir, 'monasca_user': args.user},
                   user=uid,
                   group=gid)


def modify_config(args, detected_config):
    """Compare existing and detected config for each check plugin and write out
       the plugin config if there are changes
    """
    modified_config = False

    for detection_plugin_name, new_config in detected_config.iteritems():
        if args.overwrite:
            modified_config = True
            if args.dry_run:
                continue
            else:
                agent_config.save_plugin_config(args.config_dir, detection_plugin_name, args.user, new_config)
        else:
            config = agent_config.read_plugin_config_from_disk(args.config_dir, detection_plugin_name)
            # merge old and new config, new has precedence
            if config is not None:
                # For HttpCheck, if the new input url has the same host and
                # port but a different protocol comparing with one of the
                # existing instances in http_check.yaml, we want to keep the
                # existing http check instance and replace the url with the
                # new protocol. If name in this instance is the same as the
                # url, we replace name with new url too.
                # For more details please see:
                # monasca-agent/docs/DeveloperDocs/agent_internals.md
                if detection_plugin_name == "http_check":
                    # Save old http_check urls from config for later comparison
                    config_urls = [i['url'] for i in config['instances'] if
                                   'url' in i]

                    # Check endpoint change, use new protocol instead
                    # Note: config is possibly changed after running
                    # check_endpoint_changes function.
                    config = agent_config.check_endpoint_changes(new_config, config)

                agent_config.merge_by_name(new_config['instances'], config['instances'])
                # Sort before compare, if instances have no name the sort will
                #  fail making order changes significant
                try:
                    new_config['instances'].sort(key=lambda k: k['name'])
                    config['instances'].sort(key=lambda k: k['name'])
                except Exception:
                    pass

                if detection_plugin_name == "http_check":
                    new_config_urls = [i['url'] for i in new_config['instances'] if 'url' in i]