# -*- coding: utf-8 -*-
'''
Module to provide information about minions
'''
from __future__ import absolute_import

# Import Python libs
import os
import sys
import time

# Import Salt libs
import salt.utils
import salt.key

# Import third party libs
import salt.ext.six as six
from salt.ext.six.moves import range

# Don't shadow built-ins.
__func_alias__ = {
    'list_': 'list'
}


def list_():
    '''
    Return a list of accepted, denied, unaccepted and rejected keys.
    This is the same output as `salt-key -L`

    CLI Example:

    .. code-block:: bash

        salt 'master' minion.list
    '''
    pki_dir = __salt__['config.get']('pki_dir', '')
    transport = __salt__['config.get']('transport', '')

    # We have to replace the minion/master directories
    pki_dir = pki_dir.replace('minion', 'master')

    # The source code below is (nearly) a copy of salt.key.Key.list_keys

    # We have to differentiate between RaetKey._check_minions_directories
    # and Zeromq-Keys. Raet-Keys only have three states while ZeroMQ-keys
    # have an additional 'denied' state.
    if transport in ('zeromq', 'tcp'):
        key_dirs = _check_minions_directories(pki_dir)
    else:
        key_dirs = _check_minions_directories_raetkey(pki_dir)

    ret = {}

    for dir_ in key_dirs:
        ret[os.path.basename(dir_)] = []
        try:
            for fn_ in salt.utils.isorted(os.listdir(dir_)):
                if not fn_.startswith('.'):
                    if os.path.isfile(os.path.join(dir_, fn_)):
                        ret[os.path.basename(dir_)].append(fn_)
        except (OSError, IOError):
            # key dir kind is not created yet, just skip
            continue

    return ret


def _check_minions_directories(pki_dir):
    '''
    Return the minion keys directory paths.

    This function is a copy of salt.key.Key._check_minions_directories.
    '''
    minions_accepted = os.path.join(pki_dir, salt.key.Key.ACC)
    minions_pre = os.path.join(pki_dir, salt.key.Key.PEND)
    minions_rejected = os.path.join(pki_dir, salt.key.Key.REJ)
    minions_denied = os.path.join(pki_dir, salt.key.Key.DEN)

    return minions_accepted, minions_pre, minions_rejected, minions_denied


def _check_minions_directories_raetkey(pki_dir):
    '''
    Return the minion keys directory paths.

    This function is a copy of salt.key.RaetKey._check_minions_directories.
    '''
    accepted = os.path.join(pki_dir, salt.key.RaetKey.ACC)
    pre = os.path.join(pki_dir, salt.key.RaetKey.PEND)
    rejected = os.path.join(pki_dir, salt.key.RaetKey.REJ)

    return accepted, pre, rejected


def kill(timeout=15):
    '''
    Kill the salt minion.

    timeout
        int seconds to wait for the minion to die.

    If you have a monitor that restarts ``salt-minion`` when it dies then this is
    a great way to restart after a minion upgrade.

    CLI example::

        >$ salt minion[12] minion.kill
        minion1:
            ----------
            killed:
                7874
            retcode:
                0
        minion2:
            ----------
            killed:
                29071
            retcode:
                0

    The result of the salt command shows the process ID of the minions and the
    results of a kill signal to the minion in as the ``retcode`` value: ``0``
    is success, anything else is a failure.
    '''

    ret = {
        'killed': None,
        'retcode': 1,
    }
    comment = []
    pid = __grains__.get('pid')
    if not pid:
        comment.append('Unable to find "pid" in grains')
        ret['retcode'] = salt.defaults.exitcodes.EX_SOFTWARE
    else:
        if 'ps.kill_pid' not in __salt__:
            comment.append('Missing command: ps.kill_pid')
            ret['retcode'] = salt.defaults.exitcodes.EX_SOFTWARE
        else:
            # The retcode status comes from the first kill signal
            ret['retcode'] = int(not __salt__['ps.kill_pid'](pid))

            # If the signal was successfully delivered then wait for the
            # process to die - check by sending signals until signal delivery
            # fails.
            if ret['retcode']:
                comment.append('ps.kill_pid failed')
            else:
                for _ in range(timeout):
                    time.sleep(1)
                    signaled = __salt__['ps.kill_pid'](pid)
                    if not signaled:
                        ret['killed'] = pid
                        break
                else:
                    # The process did not exit before the timeout
                    comment.append('Timed out waiting for minion to exit')
                    ret['retcode'] = salt.defaults.exitcodes.EX_TEMPFAIL

    if comment:
        ret['comment'] = comment
    return ret


def restart():
    '''
    Kill and restart the salt minion.

    The configuration key ``minion_restart_command`` is an argv list for the
    command to restart the minion.  If ``minion_restart_command`` is not
    specified or empty then the ``argv`` of the current process will be used.

    if the configuration value ``minion_restart_command`` is not set and the
    ``-d`` (daemonize) argument is missing from ``argv`` then the minion
    *will* be killed but will *not* be restarted and will require the parent
    process to perform the restart.  This behavior is intended for managed
    salt minion processes.

    CLI example::

        >$ salt minion[12] minion.restart
        minion1:
            ----------
            comment:
                - Restart using process argv:
                -     /home/omniture/install/bin/salt-minion
                -     -d
                -     -c
                -     /home/omniture/install/etc/salt
            killed:
                10070
            restart:
                ----------
                stderr:
                stdout:
            retcode:
                0
        minion2:
            ----------
            comment:
                - Using configuration minion_restart_command:
                -     /home/omniture/install/bin/salt-minion
                -     --not-an-option
                -     -d
                -     -c
                -     /home/omniture/install/etc/salt
                - Restart failed
            killed:
                10896
            restart:
                ----------
                stderr:
                    Usage: salt-minion

                    salt-minion: error: no such option: --not-an-option
                stdout:
            retcode:
                64

    The result of the command shows the process ID of ``minion1`` that is
    shutdown (killed) and the results of the restart.  If there is a failure
    in the restart it will be reflected in a non-zero ``retcode`` and possibly
    output in the ``stderr`` and/or ``stdout`` values along with addition
    information in the ``comment`` field as is demonstrated with ``minion2``.
    '''

    should_kill = True
    should_restart = True
    comment = []
    ret = {
        'killed': None,
        'restart': {},
        'retcode': 0,
    }

    restart_cmd = __salt__['config.get']('minion_restart_command')
    if restart_cmd:
        comment.append('Using configuration minion_restart_command:')
        comment.extend(['    {0}'.format(arg) for arg in restart_cmd])
    else:
        if '-d' in sys.argv:
            restart_cmd = sys.argv
            comment.append('Restart using process argv:')
            comment.extend(['    {0}'.format(arg) for arg in restart_cmd])
        else:
            should_restart = False
            comment.append('Not running in daemon mode - will not restart process after killing')

    if should_kill:
        ret.update(kill())
        if 'comment' in ret and ret['comment']:
            if isinstance(ret['comment'], six.string_types):
                comment.append(ret['comment'])
            else:
                comment.extend(ret['comment'])
        if ret['retcode']:
            comment.append('Kill failed - not restarting')
            should_restart = False

    if should_restart:
        ret['restart'] = __salt__['cmd.run_all'](restart_cmd, env=os.environ)
        # Do not want to mislead users to think that the returned PID from
        # cmd.run_all() is the PID of the new salt minion - just delete the
        # returned PID.
        if 'pid' in ret['restart']:
            del ret['restart']['pid']
        if ret['restart'].get('retcode', None):
            comment.append('Restart failed')
            ret['retcode'] = ret['restart']['retcode']
        if 'retcode' in ret['restart']:
            # Just want a single retcode
            del ret['restart']['retcode']

    if comment:
        ret['comment'] = comment

    return ret
