import logging
from contextlib import contextmanager
import paramiko

from parallels.common import MigrationError
from parallels.common import run_command
from parallels.common.utils import ssh_utils

logger = logging.getLogger(__name__)

class NestedContextSSH(object):
	"""This class is necessary not to 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):
		self._ssh_object = None
		self._ssh_settings= ssh_settings
		self._node_description = node_description

	@contextmanager
	def runner(self):
		with self.ssh() as ssh:
			yield run_command.SSHRunner(ssh, self._node_description)

	@contextmanager
	def ssh(self):
		if self._ssh_object is None:
			logger.debug("Open SSH connection to the %s", self._node_description)
			with self._connect_ssh() as ssh_object:
				self._ssh_object = ssh_object 
				try:
					yield self._ssh_object
				finally:
					self._ssh_object = None
					logger.debug("Close SSH connection to the %s", self._node_description)
		else:
			# SSH connection is already initialized by top-level contexts, no need to recreate it in nested contexts
			# It will be closed in top-level context too, so there is no need to close connection here
			logger.debug("SSH connection to the %s is already opened", self._node_description)
			yield self._ssh_object

	def _connect_ssh(self):
		try:
			return ssh_utils.connect(self._ssh_settings)
		except (paramiko.SSHException, EnvironmentError) as err:
			logger.debug(u"Exception:", exc_info=err)
			raise MigrationError(
				u"Failed to connect to the %s at '%s' by SSH as '%s': %s"
				% (self._node_description, self._ssh_settings.ip, self._ssh_settings.ssh_auth.username, err)
			)
		except Exception as err:
			logger.debug(u"Exception:", exc_info=err)
			raise MigrationError(
				u"Error while connecting to the %s at '%s' by SSH as '%s': %s"
				% (self._node_description, self._ssh_settings.ip, self._ssh_settings.ssh_auth.username, err)
			)

