from contextlib import contextmanager
from parallels.common.connections.unix_server import UnixServer
from parallels.utils import group_by_id
from parallels.common.registry import Registry
from parallels.common.connections.database_server import SourceDatabaseServer
from parallels.common.connections.ssh.connection_pool import SSHConnectionPool
from parallels.utils import cached, is_run_on_windows
from parallels.common import run_command
from parallels.common.utils import session_dir
from parallels.common.utils import migrator_utils


class SourceServer(UnixServer):
	"""This is a class to interact with source server
	and get various common information about the source server.
	"""

	def __init__(
		self, node_id, node_settings, migrator_server=None, winexe_runas=False
	):
		"""Object constructor.
		
		Args:
			node_id: server's section name specified in config.ini
			node_settings: server properties specified in config.ini
			migrator_server: server where migration tools are installed,
				do not pass if you don't need local execution of commands
				on Windows server
			winexe_runas: boolean, if True then use "run as" option when
				running commands with winexe
		"""
		self.node_id = node_id
		self.node_settings = node_settings
		self._migrator_server = migrator_server
		if not node_settings.is_windows:
			self._ssh = SSHConnectionPool.get_instance().get(
				self.node_settings, self.description()
			)
		self._winexe_runas = winexe_runas

		if self.node_settings.is_windows:
			self._session_dir = session_dir.WindowsSessionDir(
				self.runner, self.node_settings.session_dir
			)
		else:
			self._session_dir = session_dir.UnixSessionDir(
				self.runner, self.node_settings.session_dir
			)

	@contextmanager
	def runner(self):
		"""Get runner object to execute commands on the source server"""
		if self.is_windows():
			yield self._get_windows_runner()
		else:
			with self._ssh.runner() as runner:
				yield runner

	@cached
	def _get_windows_runner(self):
		if is_run_on_windows():
			if self.node_settings.agent_settings.enabled:
				return run_command.WindowsAgentRunner(
					self.node_settings, self._migrator_server, self.description()
				)
			else:
				return run_command.PAExecRunner(
					self.node_settings, self._migrator_server, self
				)
		else:
			if self._migrator_server is not None:
				local_temp_dir = self._migrator_server.session_dir()
			else:
				local_temp_dir = None

			if self.node_settings.agent_settings.enabled:
				return run_command.WindowsAgentRunner(
					self.node_settings, self._migrator_server, self.description()
				)
			else:
				return run_command.WinexeRunner(
					self.node_settings,
					local_temp_dir=local_temp_dir,
					runas=self._winexe_runas
				)

	def is_windows(self):
		"""Return True if source server is Windows server, 
		False if source server is Unix server"""
		return self.node_settings.is_windows

	def description(self):
		"""Return brief server description which should help end customer
		to identify server and which should be used in all messages"""
		return "the source server '%s' (%s)" % (self.node_id, self.ip())

	def ip(self):
		"""Return main IP address of the server"""
		return self.node_settings.ip

	def user(self):
		"""Return user we use to communicate with the server"""
		if not self.is_windows():
			return self.node_settings.ssh_auth.username
		else:
			return self.node_settings.windows_auth.username
	
	@property
	def mail_settings(self):
		"""Return copy mail content configuration"""
		return self.node_settings.mail_settings

	def settings(self):
		return self.node_settings

	def mail_imap_encryption(self):
		"""Return IMAP sync encryption mode (no encryption/SSL/TLS) 

		See parallels.common.connections.config.MailImapEncryption 
		for possible values
		"""
		return self.mail_settings.mail_imap_encryption

	def get_database_server_password(self, database_server):
		return database_server.model.password

	def get_database_server(self, model, external_db_servers):
		"""Return SourceDatabaseServer object

		Args:
			model: instance of plesk_backup.data_model.DatabaseServer
			external_db_servers: list of external database servers, specfied in config.ini
		"""
		external_db_servers_by_host = group_by_id(external_db_servers.values(), lambda s: s.host)
		if model.host in external_db_servers_by_host:
			access_settings = external_db_servers_by_host[model.host]
		else:
			context = Registry.get_instance().get_context()
			access_settings = context.conn.get_information_servers()[self.node_id]

		return SourceDatabaseServer(access_settings, model, self)

	def get_path_to_mysqldump(self):
		raise NotImplementedError()

	def get_path_to_mysql(self):
		raise NotImplementedError()

	def get_session_file_path(self, filename):
		"""Return full path to a temporary file on the server by
		short file name. Function considers session directory settings
		and ensures that session directory exists on the server"""
		return self._session_dir.get_file_path(filename)

	def session_dir(self):
		"""Return path to session directory - directory where 
		we should store all temporary files"""
		return self._session_dir.get_session_dir_path()

	@property
	@cached
	def rsync_bin(self):
		with self.runner() as runner:
			return migrator_utils.detect_rsync_path(
				runner, self.description()
			)

	def __repr__(self):
		return 'SourceServer(%r)' % (self.node_id,)

	def __hash__(self):
		"""Implemented for using objects as dicitionary keys, 
		for the purpose of grouping"""
		return hash(self.node_id)

	def __eq__(self, another):
		"""Implement for using objects as dicitionary keys, 
		for the purpose of grouping"""
		return self.node_id == another.node_id

class SingleServerPanelSourceServer(SourceServer):
	"""Source server of a single-server control panel.
	
	This represents a server, on which, from migrator point of view, all
	hosting resources are stored on local file system. This can be either a single
	server hosting panel (Plesk, Confixx), or a panel, for which each server is
	configured in a separate section of 'config.ini' file (such as Plesk Expand).
	"""

	def __init__(self, node_id, node_settings, migrator_server):
		super(SingleServerPanelSourceServer, self).__init__(
				node_id, node_settings, migrator_server
		)

	def get_unix_vhost_dirs(self, runner, domain_name):
		"""Return a list of directories with domain hosting content."""
		raise NotImplementedError()

	def get_unix_domain_mail_dirs(self, runner, domain_name):
		"""Return a list of directories with mail content."""
		raise NotImplementedError()
