"""
Interaction with the Supervisor daemon
======================================

.. code-block:: yaml

    wsgi_server:
      supervisord.running:
        - require:
          - pkg: supervisor
        - watch:
          - file: /etc/nginx/sites-enabled/wsgi_server.conf
"""

import logging

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 matches.items() 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 '{}' will be started".format(name)
                    else:
                        ret[
                            "comment"
                        ] = "The following services will be started: {}".format(
                            " ".join(to_start)
                        )
                else:
                    # Single program
                    ret["comment"] = "Service {} will be started".format(name)
            else:
                if name.endswith(":"):
                    # Process group
                    ret[
                        "comment"
                    ] = "All services in group '{}' are already running".format(name)
                else:
                    ret["comment"] = "Service {} is already running".format(name)
        else:
            ret["result"] = None
            # Process/group needs to be added
            if name.endswith(":"):
                _type = "Group '{}'".format(name)
            else:
                _type = "Service {}".format(name)
            ret["comment"] = "{} 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 "{}: updated".format(name) in result:
            just_updated = True
    elif to_add:
        # Not sure if this condition is precise enough.
        comment = "Adding service: {}".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"