#!/usr/bin/env python
"""
Management of debmirror
=======================

Create debian mirrors, using debmirror
--------------------------------------

.. code-block:: yaml
    debmirror_test1_present:
      debmirror.mirror_present:
        - name: test1

"""
import logging
import os
from functools import wraps
from salt.exceptions import CommandExecutionError, SaltInvocationError
log = logging.getLogger(__name__)


def __virtual__():
    '''
    Only load this module if debmirror is installed with deps.
    '''
    return 'debmirror'


def _test_call(method):
    (resource, functionality) = method.func_name.split('_')
    if functionality == 'present':
        functionality = 'updated'
    else:
        functionality = 'removed'

    @wraps(method)
    def check_for_testing(name, *args, **kwargs):
        if __opts__.get('test', None):
            return _no_change(name, resource, test=functionality)
        return method(name, *args, **kwargs)

    return check_for_testing


def _created(name, resource, resource_definition={}):
    changes_dict = {'name': name,
                    'changes': resource_definition,
                    'result': True,
                    'comment': '{0} {1} created'.format(resource, name)}
    return changes_dict


def _failed(name, resource, resource_definition={}):
    changes_dict = {'name': name,
                    'changes': resource_definition,
                    'result': False,
                    'comment': '{0} {1} failed to create'.format(resource,
                                                                 name)}
    return changes_dict


def _no_change(name, resource, test=False):
    changes_dict = {'name': name,
                    'changes': {},
                    'result': True}
    if test:
        changes_dict['comment'] = \
            '{0} {1} will be {2}'.format(resource, name, test)
    else:
        changes_dict['comment'] = \
            '{0} {1} is in correct state'.format(resource, name)
    return changes_dict


def _check_state(name, tgt):
    lock_file = _get_target_path(name, tgt)['lock_file']
    if os.path.isfile(lock_file) and not tgt.get('force', False):
        return _no_change(name, '{} exist=>repo locked.'.format(lock_file))
    return False


def _get_target_path(name, tgt):
    if not tgt.get('target_dir', False):
        raise SaltInvocationError('Argument "target_dir" is mandatory! ')
    return {'target_dir': tgt.get('target_dir'),
            'lock_file': tgt.get('lock_file',
                                 os.path.join(tgt.get('target_dir'),
                                              '.lockmirror')),
            'log_file': tgt.get('log_file', '/var/log/debmirror_'.join(name))}


def _get_env(tgt):
    env = {}
    for k in ['http_proxy', 'https_proxy', 'ftp_proxy', 'rsync_proxy']:
        if tgt.get(k, False):
            env[k] = tgt[k]
            env[k.upper()] = env[k]
    if tgt.get('no_proxy', False):
        env['no_proxy'] = ','.join(str(x) for x in tgt['no_proxy'])
        env['no_proxy'.upper()] = env['no_proxy']
    return env


def _get_cmdline(name, tgt):
    cmdline = " debmirror "
    if tgt.get('extra_flags'):
        cmdline += ' '.join(tgt['extra_flags'])
    if tgt.get('rsync_options'):
        rsync_options = tgt.get('rsync_options')
        if tgt.get('rsync_options_defaults', True):
            rsync_options = ['-aL', '--partial'] + rsync_options
        cmdline += " --rsync-options='" + ' '.join(rsync_options) + "'"
    if tgt.get('dist'):
        cmdline += ' --dist=' + ",".join(tgt['dist'])
    if tgt.get('section'):
        cmdline += ' --section=' + ",".join(tgt['section'])
    if tgt.get('method'):
        cmdline += ' --method=' + tgt.get('method')
    if tgt.get('mirror_host'):
        cmdline += ' --host="{}"'.format(tgt.get('mirror_host'))
    if tgt.get('mirror_root'):
        cmdline += ' --root="{}"'.format(tgt.get('mirror_root'))
    if tgt.get('arch', 'amd64'):
        cmdline += ' --arch=' + ','.join(tgt.get('arch'))
    if tgt.get('exclude_deb_section'):
        for section in tgt['exclude_deb_section']:
            cmdline += " --exclude-deb-section='" + section + "'"
    if tgt.get('filter'):
        for key, value in enumerate(sorted(tgt['filter'])):
            cmdline += " " + tgt['filter'][value]
    if tgt.get('target_dir', False):
        cmdline += ' ' + _get_target_path(name, tgt)['target_dir']
    return cmdline


def _update_mirror(name, tgt):
    # Remove old lock file, is was.
    lock_file = _get_target_path(name, tgt)['lock_file']
    if os.path.isfile(lock_file):
        log.debug("Removing lockfile:{} for mirror{}".format(lock_file, name))
        __states__['file.absent'](lock_file)
    cmdline = _get_cmdline(name, tgt)
    # fetch ENV params for proxy
    env_vars = _get_env(tgt)
    # init file logger
    l_dir = os.path.dirname(tgt['log_file'])
    if not os.path.isdir(l_dir):
        __salt__['file.makedirs'](l_dir + '/')
    fh = logging.FileHandler(
        "{0}".format(_get_target_path(name, tgt)['log_file']))
    fh.setLevel(logging.DEBUG)
    fh_format = logging.Formatter(
        '%(asctime)s - %(lineno)d - %(levelname)-8s - %(message)s')
    fh.setFormatter(fh_format)
    log2file = logging.getLogger("debmirror")
    log2file.addHandler(fh)
    result = __salt__['cmd.run_all'](cmdline, redirect_stderr=True,
                                     env=env_vars)
    log2file.debug(result['stdout'])
    # destroy file logger
    for i in list(log2file.handlers):
        log2file.removeHandler(i)
        i.flush()
        i.close()
    if result['retcode'] != 0:
        # raise CommandExecutionError(result['stderr'])
        return _failed(name,
                       "debmirror failed.Reason {0}".format(result['stderr']))
    if tgt.get('lock_target', None):
        __states__['file.touch'](lock_file)
    return _created(name, "Mirror {0} created.".format(name))


@_test_call
def mirror_present(name, **kwargs):
    '''

    :param name: mirror key name
    '''
    try:
        tgt = __salt__['config.get']('debmirror')['client']['mirrors'][name]
    except KeyError:
        comment = 'Mirror "{0}" not exist in configurathion,skipping..'.format(
            name)
        return _no_change(name, comment)

    current_state = _check_state(name, tgt)
    if not current_state:
        return _update_mirror(name, tgt)
    return current_state
