import logging
from contextlib import contextmanager
import paramiko

from parallels.core import messages
from parallels.core import run_command, MigrationError
from parallels.core.utils import ssh_utils

logger = logging.getLogger(__name__)


class BaseSSHServerConnection(object):
	"""Connection to SSH server - base class

	This class and all child classes should not create many SSH
	connections to the same server when using nested contexts.
	For example in the following example only one SSH connection will be opened:
	with nested.runner() as runner1:
		with nested.runner() as runner2:
			pass
	"""

	def __init__(self, ssh_settings, node_description):
		"""Class constructor
		Arguments:
		:param ssh_settings - object with SSH connection
		parameters (IP address, credentials).
		:param node_description - string human-readable description of the server
		"""
		self._ssh = None
		self._ssh_settings = ssh_settings
		self._node_description = node_description

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

		:rtype basestring
		"""
		return self._node_description

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

		:rtype parallels.core.run_command.SSHRunner
		"""
		with self.ssh() as ssh:
			yield run_command.SSHRunner(
				ssh, self._node_description
			)

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

		:rtype paramiko.SSHClient
		"""
		raise NotImplementedError()

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

		:rtype None
		"""
		raise NotImplementedError()

	def _connect_ssh(self):
		try:
			return ssh_utils.connect(self._ssh_settings)
		except (paramiko.SSHException, EnvironmentError) as err:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise MigrationError(
				messages.FAILED_TO_CONNECT_BY_SSH % (
					self._node_description, self._ssh_settings.ip, self._ssh_settings.ssh_auth.username, err
				)
			)
		except Exception as err:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise MigrationError(
				messages.ERROR_CONNECTING_BY_SSH % (
					self._node_description, self._ssh_settings.ip, self._ssh_settings.ssh_auth.username, err
				)
			)

