# -*- coding: utf-8 -*-
'''
Interaction with the Supervisor daemon
======================================

.. code-block:: yaml

    wsgi_server:
      supervisord.running:
        - require:
          - pkg: supervisor
        - watch:
          - file: /etc/nginx/sites-enabled/wsgi_server.conf
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import python libs
import logging
from salt.ext import six


log = logging.getLogger(__name__)


def _check_error(result, success_message):
    ret = {}

    if 'ERROR' in result:
        if any(substring in result for substring in [
            'already started',
            'not running',
            'process group already active'
        ]):
            ret['comment'] = success_message
        else:
            ret['comment'] = result
            ret['result'] = False
    else:
        ret['comment'] = success_message

    return ret


def _is_stopped_state(state):
    if state in ('STOPPED', 'STOPPING', 'EXITED', 'FATAL', 'BACKOFF'):
        return True
    if state in ('STARTING', 'RUNNING'):
        return False
    return False


def running(name,
            restart=False,
            update=False,
            user=None,
            conf_file=None,
            bin_env=None,
            **kwargs):
    '''
    Ensure the named service is running.

    name
        Service name as defined in the supervisor configuration file

    restart
        Whether to force a restart

    update
        Whether to update the supervisor configuration.

    user
        Name of the user to run the supervisorctl command

        .. versionadded:: 0.17.0

    conf_file
        path to supervisorctl config file

    bin_env
        path to supervisorctl bin or path to virtualenv with supervisor
        installed

    '''
    if name.endswith(':*'):
        name = name[:-1]

    ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}

    if 'supervisord.status' not in __salt__:
        ret['result'] = False
        ret['comment'] = 'Supervisord module not activated. Do you need to install supervisord?'
        return ret

    all_processes = __salt__['supervisord.status'](
        user=user,
        conf_file=conf_file,
        bin_env=bin_env
    )

    # parse process groups
    process_groups = set()
    for proc in all_processes:
        if ':' in proc:
            process_groups.add(proc[:proc.index(':') + 1])
    process_groups = sorted(process_groups)

    matches = {}
    if name in all_processes:
        matches[name] = (all_processes[name]['state'].lower() == 'running')
    elif name in process_groups:
        for process in (x for x in all_processes if x.startswith(name)):
            matches[process] = (
                all_processes[process]['state'].lower() == 'running'
            )
    to_add = not bool(matches)

    if __opts__['test']:
        if not to_add:
            # Process/group already present, check if any need to be started
            to_start = [x for x, y in six.iteritems(matches) if y is False]
            if to_start:
                ret['result'] = None
                if name.endswith(':'):
                    # Process group
                    if len(to_start) == len(matches):
                        ret['comment'] = (
                            'All services in group \'{0}\' will be started'
                            .format(name)
                        )
                    else:
                        ret['comment'] = (
                            'The following services will be started: {0}'
                            .format(' '.join(to_start))
                        )
                else:
                    # Single program
                    ret['comment'] = 'Service {0} will be started'.format(name)
            else:
                if name.endswith(':'):
                    # Process group
                    ret['comment'] = (
                        'All services in group \'{0}\' are already running'
                        .format(name)
                    )
                else:
                    ret['comment'] = ('Service {0} is already running'
                                      .format(name))
        else:
            ret['result'] = None
            # Process/group needs to be added
            if name.endswith(':'):
                _type = 'Group \'{0}\''.format(name)
            else:
                _type = 'Service {0}'.format(name)
            ret['comment'] = '{0} will be added and started'.format(_type)
        return ret

    changes = []
    just_updated = False

    if update:
        # If the state explicitly asks to update, we don't care if the process
        # is being added or not, since it'll take care of this for us,
        # so give this condition priority in order
        #
        # That is, unless `to_add` somehow manages to contain processes
        # we don't want running, in which case adding them may be a mistake
        comment = 'Updating supervisor'
        result = __salt__['supervisord.update'](
            user=user,
            conf_file=conf_file,
            bin_env=bin_env
        )
        ret.update(_check_error(result, comment))
        log.debug(comment)

        if '{0}: updated'.format(name) in result:
            just_updated = True
    elif to_add:
        # Not sure if this condition is precise enough.
        comment = 'Adding service: {0}'.format(name)
        __salt__['supervisord.reread'](
            user=user,
            conf_file=conf_file,
            bin_env=bin_env
        )
        # Causes supervisorctl to throw `ERROR: process group already active`
        # if process group exists. At this moment, I'm not sure how to handle
        # this outside of grepping out the expected string in `_check_error`.
        result = __salt__['supervisord.add'](
            name,
            user=user,
            conf_file=conf_file,
            bin_env=bin_env
        )

        ret.update(_check_error(result, comment))
        changes.append(comment)
        log.debug(comment)

    is_stopped = None

    process_type = None
    if name in process_groups:
        process_type = 'group'

        # check if any processes in this group are stopped
        is_stopped = False
        for proc in all_processes:
            if proc.startswith(name) \
                    and _is_stopped_state(all_processes[proc]['state']):
                is_stopped = True
                break

    elif name in all_processes:
        process_type = 'service'

        if _is_stopped_state(all_processes[name]['state']):
            is_stopped = True
        else:
            is_stopped = False

    if is_stopped is False:
        if restart and not just_updated:
            comment = 'Restarting{0}: {1}'.format(
                process_type is not None and ' {0}'.format(process_type) or '',
                name
            )
            log.debug(comment)
            result = __salt__['supervisord.restart'](
                name,
                user=user,
                conf_file=conf_file,
                bin_env=bin_env
            )
            ret.update(_check_error(result, comment))
            changes.append(comment)
        elif just_updated:
            comment = 'Not starting updated{0}: {1}'.format(
                process_type is not None and ' {0}'.format(process_type) or '',
                name
            )
            result = comment
            ret.update({'comment': comment})
        else:
            comment = 'Not starting already running{0}: {1}'.format(
                process_type is not None and ' {0}'.format(process_type) or '',
                name
            )
            result = comment
            ret.update({'comment': comment})

    elif not just_updated:
        comment = 'Starting{0}: {1}'.format(
            process_type is not None and ' {0}'.format(process_type) or '',
            name
        )
        changes.append(comment)
        log.debug(comment)
        result = __salt__['supervisord.start'](
            name,
            user=user,
            conf_file=conf_file,
            bin_env=bin_env
        )

        ret.update(_check_error(result, comment))
        log.debug(six.text_type(result))

    if ret['result'] and len(changes):
        ret['changes'][name] = ' '.join(changes)
    return ret


def dead(name,
         user=None,
         conf_file=None,
         bin_env=None,
         **kwargs):
    '''
    Ensure the named service is dead (not running).

    name
        Service name as defined in the supervisor configuration file

    user
        Name of the user to run the supervisorctl command

        .. versionadded:: 0.17.0

    conf_file
        path to supervisorctl config file

    bin_env
        path to supervisorctl bin or path to virtualenv with supervisor
        installed

    '''
    ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}

    if __opts__['test']:
        ret['result'] = None
        ret['comment'] = (
            'Service {0} is set to be stopped'.format(name))
    else:
        comment = 'Stopping service: {0}'.format(name)
        log.debug(comment)

        all_processes = __salt__['supervisord.status'](
            user=user,
            conf_file=conf_file,
            bin_env=bin_env
        )

        # parse process groups
        process_groups = []
        for proc in all_processes:
            if ':' in proc:
                process_groups.append(proc[:proc.index(':') + 1])
        process_groups = list(set(process_groups))

        is_stopped = None

        if name in process_groups:
            # check if any processes in this group are stopped
            is_stopped = False
            for proc in all_processes:
                if proc.startswith(name) \
                        and _is_stopped_state(all_processes[proc]['state']):
                    is_stopped = True
                    break

        elif name in all_processes:
            if _is_stopped_state(all_processes[name]['state']):
                is_stopped = True
            else:
                is_stopped = False
        else:
            # process name doesn't exist
            ret['comment'] = "Service {0} doesn't exist".format(name)
            return ret

        if is_stopped is True:
            ret['comment'] = "Service {0} is not running".format(name)
        else:
            result = {name: __salt__['supervisord.stop'](
                name,
                user=user,
                conf_file=conf_file,
                bin_env=bin_env
            )}
            ret.update(_check_error(result, comment))
            ret['changes'][name] = comment
            log.debug(six.text_type(result))
    return ret


def mod_watch(name,
              restart=True,
              update=False,
              user=None,
              conf_file=None,
              bin_env=None,
              **kwargs):
    '''
    The supervisord watcher, called to invoke the watch command.
    Always restart on watch

    .. note::
        This state exists to support special handling of the ``watch``
        :ref:`requisite <requisites>`. It should not be called directly.

        Parameters for this function should be set by the state being triggered.
    '''
    return running(
        name,
        restart=restart,
        update=update,
        user=user,
        conf_file=conf_file,
        bin_env=bin_env
    )
