from contextlib import contextmanager
import paramiko

from parallels.core.logging import get_logger
from parallels.core import messages, MigrationError
from parallels.core.runners.unix.ssh import SSHRunner
from parallels.core.utils import ssh_utils

logger = get_logger(__name__)


class LazyOpenSSHServerConnection(object):
    """Connection to server which is opened on first request, closed explicitly

    When you first call "ssh" or "runner" method of the class,
    connection is opened. It persists opened till you explicitly call "close"
    method.
    """

    def __init__(self, ssh_settings, server):
        """Class constructor
        Arguments:
        :param ssh_settings - object with SSH connection
        parameters (IP address, credentials).
        :param server - server object
        """
        self._server = server
        self._ssh_settings = ssh_settings
        self._contextmanager = None
        self._ssh = None
        self._runner = None

    @property
    def node_description(self):
        """Get string human-readable description of the server

        :rtype str | unicode
        """
        return self._server.description()

    @contextmanager
    def runner(self):
        """Get SSH runner to the server

        :rtype parallels.core.run_command.SSHRunner
        """
        self._initialize_ssh_objects()
        yield self._runner

    @contextmanager
    def ssh(self):
        """Get raw SSH handle to the server

        :rtype paramiko.SSHClient
        """
        self._initialize_ssh_objects()
        yield self._ssh

    def close(self):
        """Close connection to the SSH server

        :rtype None
        """
        if self._contextmanager is not None:
            logger.debug(messages.CLOSE_SSH_CONNECTION, self._server.description())
            self._contextmanager.__exit__(None, None, None)

    def _initialize_ssh_objects(self):
        if self._ssh is None:
            self._contextmanager = self._connect_ssh()
            logger.debug(messages.DEBUG_OPEN_SSH_CONNECTION, self._server.description())
            self._ssh = self._contextmanager.__enter__()
            self._runner = SSHRunner(self._ssh, self._server)

    def _connect_ssh(self):
        try:
            return ssh_utils.connect(self._ssh_settings)
        except (paramiko.SSHException, EnvironmentError) as err:
            logger.exception()
            raise MigrationError(
                messages.FAILED_TO_CONNECT_BY_SSH % (
                    self._server.description(), self._ssh_settings.ip, self._ssh_settings.ssh_auth.username, err
                )
            )
        except Exception as err:
            logger.exception()
            raise MigrationError(
                messages.ERROR_CONNECTING_BY_SSH % (
                    self._server.description(), self._ssh_settings.ip, self._ssh_settings.ssh_auth.username, err
                )
            )
