from contextlib import contextmanager
import ntpath

from parallels.core import messages
from parallels.core.connections.unix_server import UnixServer
from parallels.core.connections.database_server import SourceDatabaseServer
from parallels.core.connections.ssh.connection_pool import SSHConnectionPool
from parallels.core.utils.common import cached, is_run_on_windows
from parallels.core import run_command
from parallels.core.utils import session_dir
from parallels.core.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 messages.SOURCE_SERVER_DESCRIPTION % (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.core.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):
		"""Get source database server

		:type model: parallels.core.dump.data_model.DatabaseServer
		"""
		return SourceDatabaseServer(self.node_settings, model, self)

	def get_path_to_mysqldump(self):
		"""Get path to mysqldump.exe on that source server

		:rtype: str
		"""
		raise NotImplementedError()

	def get_path_to_mysql(self):
		"""Get path to mysql.exe on that source server

		:rtype: str
		"""
		raise NotImplementedError()

	def mysql_use_skip_secure_auth(self):
		"""Whether to pass --skip-secure-auth flag to MySQL client

		This flag is necessary when client version is greater than server version.
		Do not pass it when it is not necessary, or when client is of the same version as server.
		"""
		return False

	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()
			)

	@property
	def python_bin(self):
		if self.is_windows():
			# use Python from panel migrator agent
			return ntpath.join(self.node_settings.agent_settings.agent_path, 'python\panel-migrator-python.exe')
		else:
			# use default OS Python
			return 'python'

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

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

	def __eq__(self, another):
		"""Implement for using objects as dictionary keys,
		for the purpose of grouping"""
		return isinstance(another, SourceServer) and 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()
