import os

from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.registry import Registry
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionOptions
from parallels.core.runners.exceptions.non_zero_exit_code import NonZeroExitCodeException
from parallels.core.utils import get_thirdparties_base_path, windows_thirdparty
from parallels.core.utils.download_utils import download

logger = get_logger(__name__)


class RemoteNodeSettings(object):
    """Settings of remote node where paexec will execute commands"""

    def __init__(self, remote_server_ip, username, password):
        self.remote_server_ip = remote_server_ip
        self.username = username
        self.password = password


class PAExecCommand(object):
    """Wrapper around paexec command

    For more information on paexec, visit http://www.poweradmin.com/paexec/
    """
    def __init__(self, remote_node_settings):
        self._migrator_server = Registry.get_instance().get_context().migrator_server
        self._remote_node_settings = remote_node_settings
        self._connection_timeout = 15

        paexec_path = os.path.join(get_thirdparties_base_path(), 'paexec.exe')
        if not os.path.exists(paexec_path):
            storage_url = 'http://autoinstall.plesk.com/panel-migrator/thirdparties/paexec.exe'
            logger.info(messages.DOWNLOAD_PAEXEC.format(url=storage_url))
            download(storage_url, paexec_path)

    def run(self, executable, arguments=None, copy_program=False, do_not_wait=False, is_unchecked=False):
        passwords_file = os.path.abspath(
            self._migrator_server.get_session_file_path(
                'node-password-%s' % self._remote_node_settings.remote_server_ip
            )
        )

        thirdparties_base_path = get_thirdparties_base_path()
        command_parts = [
            r'{paexec_bin} \\{server_ip} -n {connection_timeout} -u {username} -p@ {passwords_file} '
            '-p@d'  # delete passwords file after usage
        ]
        if do_not_wait:
            command_parts.append("-d")

        if copy_program:
            command_parts.append("-c")
            # by default - overwrite if file already exists
            command_parts.append("-f")

        command_parts.append(executable)

        if arguments is not None:
            command_parts.append(arguments)

        with self._migrator_server.runner() as runner:
            runner.upload_file_content(
                passwords_file,
                self._remote_node_settings.password
            )

        command = " ".join(command_parts)
        command_args = dict(
            paexec_bin=windows_thirdparty.get_paexec_bin(),
            server_ip=self._remote_node_settings.remote_server_ip,
            connection_timeout=self._connection_timeout,
            username=self._remote_node_settings.username,
            passwords_file=passwords_file,
        )

        with self._migrator_server.runner() as runner:
            assert isinstance(runner, BaseRunner)
            if is_unchecked:
                runner.execute_command(
                    command, command_args,
                    ExecutionOptions(
                        ignore_exit_code=True,
                        working_dir=thirdparties_base_path
                    )
                )
            else:
                try:
                    runner.execute_command(
                        command, command_args, ExecutionOptions(working_dir=thirdparties_base_path)
                    )
                except NonZeroExitCodeException as e:
                    # Wrap PAExec exit code with custom exceptions
                    if e.exit_code in _paexec_exceptions:
                        logger.exception()
                        exception_class, message = _paexec_exceptions[e.exit_code]
                        raise exception_class(e, command, message)
                    else:
                        raise


class PAExecException(Exception):
    """Base class for PAExec execution errors"""

    def __init__(self, cause, command, message):
        """Class constructor

        :type message: str | unicode
        :type cause: parallels.core.runners.exceptions.non_zero_exit_code.NonZeroExitCodeException
        """
        super(PAExecException, self).__init__(messages.PAEXEC_COMMAND_FAILED.format(
            message=message, command=command, exit_code=cause.exit_code, stdout=cause.stdout, stderr=cause.stderr
        ))
        self._cause = cause

    @property
    def cause(self):
        """Original exception

        :rtype: parallels.core.runners.exceptions.non_zero_exit_code.NonZeroExitCodeException
        """
        return self._cause


class PAExecNetworkTimeoutException(PAExecException):
    """Exception meaning that connection to remote server with PAExec timed out"""
    pass


# Mapping of exit codes to messages according to https://www.poweradmin.com/paexec/
_paexec_exceptions = {
    # PAExec exit code: (exception class, reason message)
    -1: (PAExecException, 'internal error'),
    -2: (PAExecException, 'command line error'),
    -3: (PAExecException, 'failed to launch app (locally)'),
    -4: (PAExecException, 'failed to copy PAExec to remote (connection to ADMIN$ might have failed)'),
    -5: (PAExecNetworkTimeoutException, 'connection to server taking too long (timeout)'),
    -6: (PAExecException, 'PAExec service could not be installed/started on remote server'),
    -7: (PAExecException, 'could not communicate with remote PAExec service'),
    -8: (PAExecException, 'failed to copy app to remote server'),
    -9: (PAExecException, 'failed to launch app (remotely)'),
    -10: (PAExecException, 'app was terminated after timeout expired'),
    -11: (PAExecException, 'forcibly stopped with Ctrl-C / Ctrl-Break'),
}
