"""
    :copyright: Copyright 2017 by the SaltStack Team, see AUTHORS for more details.
    :license: Apache 2.0, see LICENSE for more details.


    tests.support.processes
    ~~~~~~~~~~~~~~~~~~~~~~~

    Process handling utilities
"""


import logging

from saltfactories.utils.processes import (  # pylint: disable=unused-import
    collect_child_processes,
    terminate_process,
    terminate_process_list,
)
from tests.support.cli_scripts import ScriptPathMixin, get_script_path
from tests.support.runtests import RUNTIME_VARS

try:
    from pytestsalt.fixtures.daemons import Salt as PytestSalt
    from pytestsalt.fixtures.daemons import SaltCall as PytestSaltCall
    from pytestsalt.fixtures.daemons import SaltKey as PytestSaltKey
    from pytestsalt.fixtures.daemons import SaltMaster as PytestSaltMaster
    from pytestsalt.fixtures.daemons import SaltMinion as PytestSaltMinion
    from pytestsalt.fixtures.daemons import SaltProxy as PytestSaltProxy
    from pytestsalt.fixtures.daemons import SaltRun as PytestSaltRun
    from pytestsalt.fixtures.daemons import SaltSyndic as PytestSaltSyndic
except ImportError:
    # If this happens, we are running under pytest which uninstalls pytest-salt due to impatabilites
    # These imports won't actually work but these classes are only used when running under runtests,
    # so, we're just making sure we also don't hit NameError's
    from tests.support.saltfactories_compat import SaltCallCLI as PytestSaltCall
    from tests.support.saltfactories_compat import SaltCLI as PytestSalt
    from tests.support.saltfactories_compat import SaltKeyCLI as PytestSaltKey
    from tests.support.saltfactories_compat import SaltMaster as PytestSaltMaster
    from tests.support.saltfactories_compat import SaltMinion as PytestSaltMinion
    from tests.support.saltfactories_compat import SaltProxyMinion as PytestSaltProxy
    from tests.support.saltfactories_compat import SaltRunCLI as PytestSaltRun
    from tests.support.saltfactories_compat import SaltSyndic as PytestSaltSyndic

log = logging.getLogger(__name__)


class GetSaltRunFixtureMixin(ScriptPathMixin):
    """
    Override this classes `get_salt_run_fixture` because we're still not running under pytest
    """

    def get_salt_run_fixture(self):
        pass


class Salt(ScriptPathMixin, PytestSalt):
    """
    Class which runs salt-call commands
    """

    def __init__(self, *args, **kwargs):
        super().__init__(None, *args, **kwargs)


class SaltCall(ScriptPathMixin, PytestSaltCall):
    """
    Class which runs salt-call commands
    """

    def __init__(self, *args, **kwargs):
        super().__init__(None, *args, **kwargs)


class SaltKey(ScriptPathMixin, PytestSaltKey):
    """
    Class which runs salt-key commands
    """

    def __init__(self, *args, **kwargs):
        super().__init__(None, *args, **kwargs)


class SaltRun(ScriptPathMixin, PytestSaltRun):
    """
    Class which runs salt-run commands
    """

    def __init__(self, *args, **kwargs):
        super().__init__(None, *args, **kwargs)


class SaltProxy(GetSaltRunFixtureMixin, PytestSaltProxy):
    """
    Class which runs the salt-proxy daemon
    """


class SaltMinion(GetSaltRunFixtureMixin, PytestSaltMinion):
    """
    Class which runs the salt-minion daemon
    """


class SaltMaster(GetSaltRunFixtureMixin, PytestSaltMaster):
    """
    Class which runs the salt-master daemon
    """


class SaltSyndic(GetSaltRunFixtureMixin, PytestSaltSyndic):
    """
    Class which runs the salt-syndic daemon
    """


def start_daemon(
    daemon_name=None,
    daemon_id=None,
    daemon_log_prefix=None,
    daemon_cli_script_name=None,
    daemon_config=None,
    daemon_config_dir=None,
    daemon_class=None,
    bin_dir_path=None,
    fail_hard=False,
    start_timeout=10,
    slow_stop=False,
    environ=None,
    cwd=None,
    event_listener_config_dir=None,
):
    """
    Returns a running salt daemon
    """
    # Old config name
    daemon_config["pytest_port"] = daemon_config["runtests_conn_check_port"]
    # New config name
    daemon_config["pytest_engine_port"] = daemon_config["runtests_conn_check_port"]
    request = None
    if fail_hard:
        fail_method = RuntimeError
    else:
        fail_method = RuntimeWarning
    log.info("[%s] Starting pytest %s(%s)", daemon_name, daemon_log_prefix, daemon_id)
    attempts = 0
    process = None
    get_script_path(RUNTIME_VARS.TMP_SCRIPT_DIR, daemon_cli_script_name)
    while attempts <= 3:  # pylint: disable=too-many-nested-blocks
        attempts += 1
        try:
            process = daemon_class(
                request=request,
                config=daemon_config,
                config_dir=daemon_config_dir,
                bin_dir_path=bin_dir_path,
                log_prefix=daemon_log_prefix,
                cli_script_name=daemon_cli_script_name,
                slow_stop=slow_stop,
                environ=environ,
                cwd=cwd,
                event_listener_config_dir=event_listener_config_dir,
            )
        except TypeError:
            process = daemon_class(
                request=request,
                config=daemon_config,
                config_dir=daemon_config_dir,
                bin_dir_path=bin_dir_path,
                log_prefix=daemon_log_prefix,
                cli_script_name=daemon_cli_script_name,
                slow_stop=slow_stop,
                environ=environ,
                cwd=cwd,
            )
        process.start()
        if process.is_alive():
            try:
                connectable = process.wait_until_running(timeout=start_timeout)
                if connectable is False:
                    connectable = process.wait_until_running(timeout=start_timeout / 2)
                    if connectable is False:
                        process.terminate()
                        if attempts >= 3:
                            fail_method(
                                "The pytest {}({}) has failed to confirm running status "
                                "after {} attempts".format(
                                    daemon_name, daemon_id, attempts
                                )
                            )
                        continue
            except Exception as exc:  # pylint: disable=broad-except
                log.exception("[%s] %s", daemon_log_prefix, exc, exc_info=True)
                terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
                if attempts >= 3:
                    raise fail_method(str(exc))
                continue
            log.info(
                "[%s] The pytest %s(%s) is running and accepting commands "
                "after %d attempts",
                daemon_log_prefix,
                daemon_name,
                daemon_id,
                attempts,
            )

            break
        else:
            terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
            continue
    else:
        if process is not None:
            terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
        raise fail_method(
            "The pytest {}({}) has failed to start after {} attempts".format(
                daemon_name, daemon_id, attempts - 1
            )
        )
    return process
