from parallels.plesk.source.plesk import messages
import os
import textwrap

import parallels
from parallels.core.utils import migrator_utils
from parallels.core.utils.pmm.agent import DumpAll, DumpSelected
from parallels.plesk.source.legacy.pmm_agent import UnixPmmMigrationAgent
from parallels.core.utils.pmm.windows_agent import WindowsPleskPmmMigrationAgent
from parallels.core import MigrationError
from parallels.core.utils.common import is_empty_list


class PleskXPmmMigrationAgent(UnixPmmMigrationAgent):
	"""Migration agent for Plesk panel and Expand.

	'self.max_pleskversion' specifies maximum version of source Plesk panel
	that is supported by the agent.
	"""
	def __init__(self, global_context, server, migrator_pmm_dir, settings):
		self.max_pleskversion = {'MAJOR': 12, 'MINOR': 5, 'PATCH': 30}
		self.dump_directories = 'clients domains resellers .discovered'
		self.remote_log_filename = 'dump.log'
		cli_options = '--dump-all --configuration-only --server -v 5'
		super(PleskXPmmMigrationAgent, self).__init__(global_context, server, migrator_pmm_dir, settings, cli_options)

	@property
	def common_pmm_dir(self):
		target_plesk_version = self.global_context.conn.target.plesk_server.get_plesk_version()
		if target_plesk_version < (12, 5):
			shared_dir = 'plesk_11_5_pmm_shared'
		else:
			shared_dir = 'plesk_12_5_pmm_shared'

		return migrator_utils.get_package_extras_file_path(parallels.plesk.source.legacy, shared_dir)

	def _run(self, local_data_filename, local_log_filename, selection=DumpAll()):
		"""Run migration agent on a source panel and download the results.
		
		Execute the agent which puts results into XML dump file.
		Download dump XML file and log file to specified locations:
		- dump_xml_filename - local filename to put dump XML to
		- dump_log_filename - local filename to put dump log to

		Raise MigrationError in case of any failure.
		"""
		self.logger.info(messages.LOG_CREATE_SOURCE_PANEL_MIGRATION_DUMP)
		self.local_log_filename = local_log_filename
		self._patch_template(self.max_pleskversion)
		self.logger.debug(messages.LOG_RUN_MIGRATION_AGENT)
		self._make_dump(selection)
		self._download_dump(local_data_filename)

	def _run_shallow(self, local_data_filename, local_log_filename):
		self.logger.info(messages.LOG_CREATE_SOURCE_PANEL_MIGRATION_SHALLOW_DUMP)
		self.local_log_filename = local_log_filename
		self._patch_template(self.max_pleskversion)
		self.logger.debug(u"Run migration agent.")
		self._make_shallow_dump()
		self._download_agent_file('dump.xml', local_data_filename)

	def _run_capability(self, local_data_filename, local_log_filename, selection=DumpAll()):
		self.logger.info(messages.LOG_CREATE_SOURCE_PANEL_MIGRATION_CAPABILITY_DUMP)
		self.local_log_filename = local_log_filename
		self._patch_template(self.max_pleskversion)
		self.logger.debug(messages.LOG_RUN_MIGRATION_AGENT)
		self._make_capability_dump(selection)
		self._download_agent_file('dump.xml', local_data_filename)

	def _patch_template(self, max_pleskversion):
		"""Fill in maximum supported Plesk version."""
		command = (
			u"""
			sed "s/@@MAJOR_VERSION@@/{MAJOR}/;
			s/@@MINOR_VERSION@@/{MINOR}/;
			s/@@PATCH_VERSION@@/{PATCH}/;" \\
			./PleskMaxVersion.pm.in > ./PleskMaxVersion.pm""".format(
				**max_pleskversion
			)
		)
		self._run_agent_command(
			u"cd {path}; %s" % textwrap.dedent(command),
			{'path': self.agent_dir})

	def _make_dump(self, selection=DumpAll()):
		"""Run the agent and create migration dump.

		Download agent log file even in case, if the agent has failed.
		"""
		self.logger.debug(messages.LOG_RUN_MIGRATION_AGENT)
		command = u"""cd {path}; perl {script} %s > {log} 2>&1"""

		cli_options = ['--configuration-only', '--server', '-v', '5'] + self._get_selection_cli_options(selection)

		try:
			self._run_agent_command(
				command % (" ".join(cli_options)),
				{
					'path': self.agent_dir,
					'script': self.settings.agent_script_name,
					'log': self.remote_log_filename,
				}
			)
		finally:
			self._download_agent_file(
				self.remote_log_filename, self.local_log_filename
			)

	def _make_shallow_dump(self):
		"""Run the agent and create shallow migration dump.

		Download agent log file even in case, if the agent has failed.
		"""
		self.logger.debug(messages.LOG_RUN_MIGRATION_AGENT)
		command = u"""cd {path}; perl {script} %s > {log} 2>&1"""

		cli_options = [
			'--dump-all',
			'--configuration-only',
			'--server',
			'-v', '5',
			'-nc'
		]

		try:
			self._run_agent_command(
				command % (" ".join(cli_options)),
				{
					'path': self.agent_dir,
					'script': self.settings.agent_script_name,
					'log': self.remote_log_filename,
				}
			)
		finally:
			self._download_agent_file(
				self.remote_log_filename, self.local_log_filename
			)

	def _make_capability_dump(self, selection=DumpAll()):
		"""Run the agent and create capability dump.

		Download agent log file even in case, if the agent has failed.
		"""
		self.logger.debug(messages.LOG_RUN_MIGRATION_AGENT)
		command = u"""cd {path}; perl {script} %s > {log} 2>&1"""

		cli_options = ['--capability-only', '-v', '5'] + self._get_selection_cli_options(selection)

		try:
			self._run_agent_command(
				command % (" ".join(cli_options)),
				{
					'path': self.agent_dir,
					'script': self.settings.agent_script_name,
					'log': self.remote_log_filename,
				}
			)
		finally:
			self._download_agent_file(
				self.remote_log_filename, self.local_log_filename
			)

	@staticmethod
	def _get_selection_cli_options(selection):
		selection_cli_options = []
		if isinstance(selection, DumpAll):
			selection_cli_options.append('--dump-all')
		elif isinstance(selection, DumpSelected):
			if not is_empty_list(selection.domains):
				selection_cli_options.append('--dump-domains=%s' % ",".join(selection.domains))
			if not is_empty_list(selection.clients):
				selection_cli_options.append('--dump-clients=%s' % ",".join(selection.clients))
			if not is_empty_list(selection.resellers):
				selection_cli_options.append('--dump-resellers=%s' % ",".join(selection.resellers))
		return selection_cli_options

	def _download_dump(self, local_data_filename):
		self.logger.debug(messages.LOG_DOWNLOAD_DUMP_XML, local_data_filename)
		remote_dump_file = 'migration_dump.tar'
		command = (ur"""
			cd {path}; rm -f {dumpfile};
			for i in %s ;
				do test -d $i && tar rf {dumpfile} $i ;
			done;
			tar rf {dumpfile} backup_info*""")
		self._run_agent_command(
			command % self.dump_directories,
			{
				'path':	self.agent_dir,
				'dump_directories': self.dump_directories,
				'dumpfile': remote_dump_file,
			}
		)
		self._download_agent_file(remote_dump_file, local_data_filename)

	def _download_agent_file(self, remote_file, local_file):
		"""Download a file from agent directory on source server."""
		try:
			with self._source_server.runner() as runner:
				self.logger.debug(messages.LOG_DOWNLOAD_FILE_TO, local_file)
				runner.get_file( 
					os.path.join(
						self.agent_dir, remote_file), local_file)
		except Exception as e:
			self.logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			self.logger.error(messages.ERROR_DOWNLOADING_FILE, e)

	def _run_agent_command(self, command, arguments):
		"""Run a command on the source server."""
		try:
			with self._source_server.runner() as runner:
				runner.sh(textwrap.dedent(command), arguments)
		except Exception as e:
			self.logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise MigrationError(messages.ERROR_RUNNING_COMMAND % e)


def create_pmm_agent(global_context, source_server):
	"""Initialize and return instance of PmmMigrationAgentBase.

	Args:
		global_context:
			Registry, instance of GlobalMigrationContext.
		source_settings:
			Source server settings read from a section of config.ini.
	Returns:
		An instance of PmmMigrationAgentBase.
	"""
	if source_server.is_windows():
		return WindowsPleskPmmMigrationAgent(global_context, source_server)
	else:
		migrator_pmm_dir = migrator_utils.get_package_extras_file_path(
			parallels.plesk.source.plesk, get_pmm_agent_dir(global_context)
		)
		return PleskXPmmMigrationAgent(
			global_context,
			source_server,
			migrator_pmm_dir,
			PleskPMMConfig(source_server.node_settings)
		)


def get_pmm_agent_dir(global_context):
	"""
	:type global_context: parallels.core.global_context.GlobalMigrationContext
	:rtype: str
	"""
	target_plesk_version = global_context.conn.target.plesk_server.get_plesk_version()
	if target_plesk_version < (12, 5):
		return 'pmm_11_5'
	else:
		return 'pmm_12_5'


class PleskPMMConfig(object):
	def __init__(self, settings):
		self.ip = settings.ip
		self.id = settings.id
		self.is_windows = settings.is_windows
		self.ssh_auth = settings.ssh_auth
		self.session_dir = settings.session_dir
		self.agent_home = os.path.join(self.session_dir, 'migration_agent')
		self.pmm_agent_name = "PleskX"
		self.agent_script_name = 'PleskX.pl'
