# -*- coding: utf-8 -*-

#########################################################################
# pyeole.systemd._systemd - manage Systemd
# Copyright © 2016 Pôle de Compétence EOLE <eole@ac-dijon.fr>
#
# License CeCILL:
#  * in french: http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
#  * in english http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
#########################################################################

"""EOLE Systemd

This internal module is only for use by :mod:`pyeole.systemd`.

"""

from glob import glob

from os import unlink, symlink
from os.path import dirname
from os.path import isfile
from os.path import join
from os.path import relpath

import re

import logging

# Base exception
from pyeole.service.error import ServiceError
# Configuration
from pyeole.service.error import ConfigureError
from pyeole.service.error import DisabledError
from pyeole.service.error import UnknownServiceError
# Action
from pyeole.service.error import StartError
from pyeole.service.error import StopError
from pyeole.service.launcher import Command

_DISTRIB_RUNLEVEL = 2
_DISTRIB_STOP_RUNLEVEL = 0
_DISTRIB_DEFAULT_START_LEVEL = 20
_DISTRIB_DEFAULT_STOP_LEVEL = 20
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())


_SYSTEMD_CMD = u'/bin/systemctl'

#####
##### Per action workers
#####

class Service(Command):
    module_name = u'Systemd'
    multiple = True

    def cmd_check_service(self, service, ctx):
        code, stdin, err = self._exec_systemd('cat', service, ctx)
        if code != 0:
            msg = u'Missing Systemd unit for service {0} in {1}'
            name = self._getname(service)
            cont = ctx[u'name']
            raise UnknownServiceError(msg.format(name, cont))

    def cmd_disable_service(self, service, ctx):
        code, stdin, err = self._exec_systemd('disable', service, ctx)
        if code != 0:
            msg = u'Can not disable Systemd service {0} in {1}: {2}'
            name = self._getname(service)
            cont = ctx[u'name']
            raise ConfigureError(msg.format(name, cont, err))
        self.cmd_mask_service(service, ctx)
        self._exec_systemd('reset-failed', service, ctx)

    def cmd_enable_service(self, service, ctx):
        self.cmd_disable_service(service, ctx)
        self.cmd_unmask_service(service, ctx)
        code, stdin, err = self._exec_systemd('enable', service, ctx)
        if code != 0:
            msg = u'Can not enable Systemd service {0} in {1}: {2}'
            name = self._getname(service)
            cont = ctx[u'name']
            raise ConfigureError(msg.format(name, cont, err))

    def cmd_mask_service(self, service, ctx):
        code, stdin, err = self._exec_systemd('mask', service, ctx)
        if code != 0:
            msg = u'Can not mask Systemd service {0} in {1}: {2}'
            name = self._getname(service)
            cont = ctx[u'name']
            # Do not raise for overridden services (#21225)
            log.debug(msg.format(name, cont, err))

    def cmd_unmask_service(self, service, ctx):
        code, stdin, err = self._exec_systemd('unmask', service, ctx)
        if code != 0:
            msg = u'Can not unmask Systemd service {0} in {1}: {2}'
            name = self._getname(service)
            cont = ctx[u'name']
            raise ConfigureError(msg.format(name, cont, err))

    def cmd_status_service(self, service, ctx):
        self._do_systemd(u'status', service, ctx)


    def cmd_start_service(self, service, ctx, message):
        self._do_systemd(u'start', service, ctx, message=message)


    def cmd_stop_service(self, service, ctx, message):
        self._do_systemd(u'stop', service, ctx, message=message)


    def cmd_restart_service(self, service, ctx, message=None):
        if not message:
            message = "restarting"
        self._do_systemd(u'restart', service, ctx, message=message)


    def cmd_reload_service(self, service, ctx, message=None):
        self._do_systemd(u'reload', service, ctx, message=message)


    #####
    ##### Systemd catch action
    #####


    def _do_systemd(self, action, service, ctx, message=None):
        """Perform action for Systemd service.

        Check for return code of the check command or output.
        Some init scripts status functions have some non-standard output.

        :param action: action to perform
        :type action: `str` in [``status``, ``start``, ``restart``,
                      ``stop``, ``reload``]
        :param service: service informations
        :type service: `dict`
        :param ctx: container context
        :type ctx: `dict`
        :raise StartError: if start, restart or reload is not
                                  confirmed
        :raise StopError: if stop is not confirmed
        :raise ServiceError: if another :data:`action` is not confirmed

        """
        name = self._getname(service)
        cont = ctx[u'name']
        if action in [u'start', u'restart', u'reload']:
            code_ok = [0]
            error_string = u'Service {0} in {1} not started: {2}'
            service_exc = StartError
            display_status = 'started'
        elif action == u'stop':
            code_ok = [1, 3]
            error_string = u'Service {0} in {1} not stopped: {2}'
            service_exc = StopError
            display_status = 'stopped'
        elif action == u'status':
            code_ok = [0]
            error_string = u'Unable to get status of'
            error_string += u' Systemd service {0} in {1}: {2}'
            service_exc = ServiceError
            display_status = None
        elif action == u'mask':
            code_ok = [0]
            error_string = u'Service {0} in {1} not masked: {2}'
            service_exc = ServiceError
            display_status = 'masked'
        elif action == u'unmask':
            code_ok = [0]
            error_string = u'Service {0} in {1} not unmasked: {2}'
            service_exc = ServiceError
            display_status = 'unmasked'
        else:
            code_ok = [0]
            error_string = u'Error: {2}'
            service_exc = ServiceError

        if isinstance(service, list):
            progress = True
        else:
            progress = False
        action_code, action_stdout, action_stderr = self._exec_systemd(action, service, ctx, progress=progress, message=message)
        if action == u'status':
            code = action_code
            stderr = ''
            stdout = ''
        else:
            if isinstance(service, list):
                stderr = []
                code = code_ok
                for srv in service:
                    ret = self.status_service(srv, ctx, display_status=display_status)
                    if ret['code'] != code_ok:
                        code = ret['code']
                        stderr.append(ret['msg'])
                stderr = '\n'.join(stderr)
                stdout = ''
            else:
                code, stdout, stderr = self._exec_systemd('status', service, ctx)

        # Check return code of action or result of “status”
        if not code in code_ok:
            output = action_stderr or stderr or stdout
            raise service_exc(error_string.format(name, cont, output))

    def _exec_systemd(self, action, service, ctx, progress=False, message=None):
        cmd = [_SYSTEMD_CMD, '--no-pager', action]
        if isinstance(service, list):
            for srv in service:
                cmd.append(srv['name'])
        else:
            cmd.append(service['name'])
        return self._exec_cmd(cmd, service, ctx, progress=progress, message=message)
