import typing

import crmsh.parallax
import crmsh.sh
from crmsh import utils


class ServiceManager(object):
    """
    Class to manage systemctl services
    """

    def __init__(self, shell: crmsh.sh.ClusterShell = None):
        self._shell = crmsh.sh.cluster_shell() if shell is None else shell

    def service_is_available(self, name, remote_addr=None):
        """
        Check whether service is available
        """
        return 0 == self._run_on_single_host("systemctl list-unit-files '{}'".format(name), remote_addr)

    def service_is_enabled(self, name, remote_addr=None):
        """
        Check whether service is enabled
        """
        return 0 == self._run_on_single_host("systemctl is-enabled '{}'".format(name), remote_addr)

    def service_is_active(self, name, remote_addr=None):
        """
        Check whether service is active
        """
        return 0 == self._run_on_single_host("systemctl is-active '{}'".format(name), remote_addr)

    def start_service(self, name, enable=False, remote_addr=None, node_list=[]):
        """
        Start service
        Return success node list
        """
        if enable:
            cmd = "systemctl enable --now '{}'".format(name)
        else:
            cmd = "systemctl start '{}'".format(name)
        return self._call(remote_addr, node_list, cmd)

    def _call(self, remote_addr: str, node_list: typing.List[str], cmd: str) -> typing.List[str]:
        assert not (bool(remote_addr) and bool(node_list))
        if len(node_list) == 1:
            remote_addr = node_list[0]
            node_list = list()
        if node_list:
            results = ServiceManager._call_with_parallax(cmd, node_list)
            return [host for host, result in results.items() if isinstance(result, tuple) and result[0] == 0]
        else:
            rc = self._run_on_single_host(cmd, remote_addr)
            if rc == 0:
                return [remote_addr or utils.this_node()]
            else:
                return list()

    def _run_on_single_host(self, cmd, host):
        rc, _, _ = self._shell.get_rc_stdout_stderr_without_input(host, cmd)
        if rc == 255:
            raise ValueError("Failed to run command on host {}: {}".format(host, cmd))
        return rc

    @staticmethod
    def _call_with_parallax(cmd, host_list):
        ret = crmsh.parallax.parallax_run(host_list, cmd)
        if ret is crmsh.parallax.Error:
            raise ret
        return ret

    def stop_service(self, name, disable=False, remote_addr=None, node_list=[]):
        """
        Stop service
        Return success node list
        """
        if disable:
            cmd = "systemctl disable --now '{}'".format(name)
        else:
            cmd = "systemctl stop '{}'".format(name)
        return self._call(remote_addr, node_list, cmd)

    def enable_service(self, name, remote_addr=None, node_list=[]):
        """
        Enable service
        Return success node list
        """
        cmd = "systemctl enable '{}'".format(name)
        return self._call(remote_addr, node_list, cmd)

    def disable_service(self, name, remote_addr=None, node_list=[]):
        """
        Disable service
        Return success node list
        """
        cmd = "systemctl disable '{}'".format(name)
        return self._call(remote_addr, node_list, cmd)
