from parallels.common import messages
import logging
import ntpath
import re
from xml.etree import ElementTree

from parallels.common.utils.migrator_utils import split_unix_path
from parallels.common.utils.migrator_utils import upload_temporary_file_content
from parallels.common.utils.windows_utils import cmd_command

logger = logging.getLogger(__name__)


class PMMCLICommandBase(object):
	"""Wrapper around "pmmcli" command"""

	def __init__(self, server):
		self._server = server

	@property
	def _pmmcli_bin(self):
		"""Get full path to pmmcli.exe binary"""
		raise NotImplementedError()

	def import_dump(self, backup_path):
		"""Call pmmcli --import-file-as-dump for specified backup

		:param backup_path: path to backup archive
		:return: ImportResult
		"""
		raise NotImplementedError()

	def restore(self, dump_xml_path, env=None):
		"""Call pmmcli --restore for specified dump XML

		Arguments:
		:param dump_xml_path: path to domain backup XML
		:param env: additional environment variables as dictionary

		Returns: RestoreResult
		"""
		raise NotImplementedError()

	def get_task_status(self, task_id):
		"""Request restoration task status.

		Args:
		task_id: ID of a started restore task.

		Returns:
		RestoreTaskStatus object representing current restore status
		"""
		raise NotImplementedError()

	def delete_dump(self, backup_path, env=None):
		"""Call pmmcli --delete-dump for specified backup

		:param backup_path: path to backup archive
		:param env: additional environment variables as dictionary
		:return: None
		"""
		raise NotImplementedError()

	def _get_import_dump_request(self, source_dir, source_file, target_dir):
		return get_import_dump_request(source_dir, source_file, target_dir)

	def _get_restore_dump_request(self, xml_root_dir, xml_filename):
		return get_restore_dump_request(xml_root_dir, xml_filename)

	def _get_delete_dump_request(self, dump_dir, relative_path_to_xml_filename):
		return get_delete_dump_request(dump_dir, relative_path_to_xml_filename)


class PMMCLICommandUnix(PMMCLICommandBase):
	"""Wrapper around "pmmcli" command for Unix"""

	@property
	def _pmmcli_bin(self):
		"""Get full path to pmmcli binary"""
		return self._server.plesk_dir + u"/admin/bin/pmmcli"

	def import_dump(self, backup_path):
		"""Call pmmcli --import-file-as-dump for specified backup

		:param backup_path: path to backup archive
		:return: ImportResult
		"""
		request_root_dir, request_file_name = split_unix_path(backup_path)
		import_dump_request_xml = self._get_import_dump_request(
			source_dir=request_root_dir,
			source_file=request_file_name,
			target_dir=self._server.dump_dir
		)
		logger.debug(u"Import dump request XML: %s", import_dump_request_xml)

		with self._server.runner() as runner:
			stdout = runner.sh(
				u'PPA_IGNORE_FILE_MATCHING=true %s/admin/bin/pmmcli --import-file-as-dump' % (self._server.plesk_dir,),
				stdin_content=import_dump_request_xml
			)

		return ImportResult(stdout)

	def restore(self, dump_xml_path, env=None):
		"""Call pmmcli --restore for specified dump XML

		Arguments:
		:param dump_xml_path: path to domain backup XML
		:param env: additional environment variables as dictionary

		Returns: RestoreResult
		"""
		backup_xml_directory, backup_xml_filename = split_unix_path(
			dump_xml_path
		)
		restore_request_xml = self._get_restore_dump_request(
			backup_xml_directory,
			backup_xml_filename,
		)
		logger.debug("Restore request XML: %s", restore_request_xml)

		with self._server.runner() as runner:
			stdout = runner.sh(
				u'%s%s/admin/bin/pmmcli --restore' % (self._get_env_str(env), self._server.plesk_dir,),
				stdin_content=restore_request_xml
			)

		return RestoreResult(stdout)

	def get_task_status(self, task_id):
		"""Request restoration task status.

		Args:
		task_id: ID of a started restore task.

		Returns:
		RestoreTaskStatus object representing current restore status
		"""
		with self._server.runner() as runner:
			stdout = runner.sh(
				u'{panel_homedir}/admin/bin/pmmcli --get-task-status {task_id}',
				dict(
					panel_homedir=self._server.plesk_dir,
					task_id=task_id
				)
			)
		return RestoreTaskStatus(stdout)

	def delete_dump(self, backup_path, env=None):
		"""Call pmmcli --delete-dump for specified backup

		:param backup_path: path to backup archive
		:param env: additional environment variables as dictionary
		:return: None
		"""

		logger.debug(messages.REMOVE_BACKUP_FILE_S_FROM_TARGET % backup_path)
		remove_dump_xml = self._get_delete_dump_request(self._server.dump_dir, backup_path)
		logger.debug(u"Delete dump request XML: %s" % remove_dump_xml)
		with self._server.runner() as runner:
			runner.sh(
				u'%s%s --delete-dump' % (self._get_env_str(env), self._pmmcli_bin),
				stdin_content=remove_dump_xml
			)

	@staticmethod
	def _get_env_str(env):
		if env:
			return u'%s ' % u' '.join(u'%s=%s' % (name, value) for name, value in env.iteritems())
		else:
			return u''


class PMMCLICommandWindows(PMMCLICommandBase):
	def import_dump(self, backup_path):
		"""Call pmmcli --import-file-as-dump for specified backup

		:param backup_path: path to backup archive
		:return: ImportResult
		"""
		logger.debug(messages.IMPORT_BACKUP_TARGET_PANEL_PMM_REPOSITORY)
		backup_directory = ntpath.dirname(backup_path)
		backup_filename = ntpath.basename(backup_path)
		import_dump_request_xml = self._get_import_dump_request(
			source_dir=backup_directory,
			source_file=backup_filename,
			target_dir=self._server.dump_dir
		)
		request_xml_filename = ntpath.join(
			self._server.plesk_dir, u'panel-transfer-import-request.xml'
		)
		with self._server.runner() as runner:
			with upload_temporary_file_content(
				runner, request_xml_filename, import_dump_request_xml
			):
				full_env = dict(PPA_IGNORE_FILE_MATCHING=u'true', PLESK_DIR=self._server.plesk_dir)
				stdout = runner.sh(cmd_command(
					u'{env}"{pmmcli}" --import-file-as-dump < "{request}"'.format(
						env=self._get_env_str(full_env),
						pmmcli=self._pmmcli_bin,
						request=request_xml_filename
					)
				))

		return ImportResult(stdout)

	def restore(self, dump_xml_path, env=None):
		"""Call pmmcli --restore for specified dump XML

		Arguments:
		:param dump_xml_path: path to domain backup XML
		:param env: additional environment variables as dictionary

		Returns: RestoreResult
		"""
		backup_xml_directory = ntpath.dirname(dump_xml_path)
		backup_xml_filename = ntpath.basename(dump_xml_path)
		restore_request_xml = self._get_restore_dump_request(
			backup_xml_directory, backup_xml_filename
		)
		request_xml_filename = ntpath.join(
			self._server.plesk_dir, u'panel-transfer-restore-request.xml'
		)

		with self._server.runner() as runner:
			with upload_temporary_file_content(
				runner, request_xml_filename, restore_request_xml
			):
				full_env=dict(PPA_IGNORE_FILE_MATCHING=u'true', PLESK_DIR=self._server.plesk_dir)
				if env:
					full_env.update(env)
				stdout = runner.sh(cmd_command(
					u'{env}"{pmmcli}" --restore < "{request}"'.format(
						env=self._get_env_str(full_env),
						pmmcli=self._pmmcli_bin,
						request=request_xml_filename,
					)
				))

		return RestoreResult(stdout)

	def get_task_status(self, task_id):
		"""Request restoration task status.

		Args:
		task_id: ID of a started restore task.

		Returns:
		RestoreTaskStatus object representing current restore status
		"""
		with self._server.runner() as runner:
			full_env=dict(PLESK_DIR=self._server.plesk_dir)
			stdout = runner.sh(cmd_command(
				u'{env}"{pmmcli}" --get-task-status {task_id}'.format(
					env=self._get_env_str(full_env),
					pmmcli=self._pmmcli_bin,
					task_id=task_id,
				)
			))
		return RestoreTaskStatus(stdout)

	def delete_dump(self, backup_path, env=None):
		"""Call pmmcli --delete-dump for specified backup
		:param backup_path: path to backup archive
		:param env: additional environment variables as dictionary
		:return: None
		"""

		logger.debug(messages.REMOVE_BACKUP_FILE_S_FROM_TARGET_1 % backup_path)
		remove_dump_xml = self._get_delete_dump_request(self._server.dump_dir, backup_path)
		logger.debug(u"Delete dump request XML: %s" % remove_dump_xml)

		request_xml_filename = ntpath.join(
			self._server.plesk_dir, u'panel-transfer-restore-request.xml'
		)
		with self._server.runner() as runner:
			with upload_temporary_file_content(
				runner, request_xml_filename, remove_dump_xml
			):
				full_env = dict(PLESK_DIR=self._server.plesk_dir)
				if env:
					full_env.update(env)
				runner.sh(cmd_command(
					u'{env}"{pmmcli}" --delete-dump < "{request}"'.format(
						env=self._get_env_str(full_env),
						pmmcli=self._pmmcli_bin,
						request=request_xml_filename,
					)
				))

	@property
	def _pmmcli_bin(self):
		"""Get full path to pmmcli.exe binary"""
		return ntpath.join(self._server.plesk_dir, ur"admin\bin\pmmcli.exe")

	@staticmethod
	def _get_env_str(env):
		if env:
			return u'%s&&' % u'&&'.join(u'SET %s=%s' % (name, value) for name, value in env.iteritems())
		else:
			return u''


class ImportResult(object):
	def __init__(self, response_str):
		self._response_str = response_str
		self._xml = ElementTree.fromstring(response_str.encode('utf-8'))

	@property
	def raw_stdout(self):
		return self._response_str

	@property
	def error_code(self):
		return self._xml.findtext('errcode')

	@property
	def backup_xml_file_name(self):
		return self._xml.find('data/dump').attrib['name']

	@property
	def backup_id(self):
		match = re.search(r'(\d+)\.xml$', self.backup_xml_file_name)
		if match is None:
			return None
		else:
			backup_id = match.group(1)
			return backup_id

	@property
	def backup_prefix(self):
		return self.backup_xml_file_name[:self.backup_xml_file_name.find('_')]


class RestoreResult(object):
	def __init__(self, response_str):
		self._response_str = response_str
		self._restore_result_xml = ElementTree.fromstring(response_str.encode('utf-8'))

	@property
	def raw_stdout(self):
		return self._response_str

	@property
	def task_id(self):
		return self._restore_result_xml.findtext('./data/task-id')


class RestoreTaskStatus(object):
	def __init__(self, response_str):
		self._response_str = response_str
		xml = ElementTree.fromstring(response_str.encode('utf-8'))
		self._status_xml = xml.find('data/task-status/mixed')
		self._stopped_xml = xml.find('data/task-status/stopped')

	@property
	def raw_stdout(self):
		return self._response_str

	@property
	def is_running(self):
		"""Whether restoration process is running or finished."""
		if self.is_interrupted:
			return False

		if self._status_xml is not None and 'status' in self._status_xml.attrib:
			# 'status' node is '<mixed status="success" ...>'
			logger.debug(u'Restore task finished')
			return False
		else:
			# 'status' node is '<mixed>'
			logger.debug(u'Restore task is running')
			return True

	@property
	def is_interrupted(self):
		return self._stopped_xml is not None

	@property
	def log_location(self):
		"""Path to restoration log"""
		if self._status_xml is None:
			return None
		else:
			return self._status_xml.attrib.get('log-location')


def get_delete_dump_request(dump_dir, relative_path_to_xml_filename):
	"""Create XML that should be passed to pmmcli.exe --delete-dump

	Return XML as a string"""

	return """<?xml version="1.0"?>
<delete-dump-query>
	<dump-specification>
		<dumps-storage-credentials storage-type="local">
			<root-dir>{dump_dir}</root-dir>
			<file-name/>
		</dumps-storage-credentials>
		<name-of-info-xml-file>{file_name}</name-of-info-xml-file>
	</dump-specification>
	<object-specification type="server" guid="00000000-0000-0000-0000-000000000000"/>
</delete-dump-query>
	""".format(
		dump_dir=dump_dir,
		file_name=relative_path_to_xml_filename
	)


def get_restore_dump_request(xml_root_dir, xml_filename):
	"""Create XML that should be passed to pmmcli.exe --restore

	Return XML as a string"""

	return """<?xml version="1.0"?>
<restore-task-description owner-guid="" owner-type="server">
	<source>
		<dump-specification>
			<dumps-storage-credentials storage-type="local">
				<root-dir>{xml_root_dir}</root-dir>
				<file-name/>
			</dumps-storage-credentials>
			<name-of-info-xml-file>{xml_filename}</name-of-info-xml-file>
		</dump-specification>
	</source>
	<objects>
		<selected>
			<node children-processing-type="copy" name="domain"/>
			<node children-processing-type="copy" name="dump-info"/>
		</selected>
	</objects>
	<ignore-errors>
		<ignore-error type="sign"/>
	</ignore-errors>
	<misc delete-downloaded-dumps="false" suspend="false" verbose-level="5"/>
	<conflict-resolution-rules>
		<policy>
			<timing>
				<resolution>
					<overwrite/>
				</resolution>
			</timing>
			<resource-usage>
				<resolution>
					<overuse/>
				</resolution>
			</resource-usage>
			<configuration>
				<resolution>
					<automatic/>
				</resolution>
			</configuration>
		</policy>
	</conflict-resolution-rules>
</restore-task-description>""".format(
		xml_root_dir=xml_root_dir,
		xml_filename=xml_filename,
	)



def get_import_dump_request(source_dir, source_file, target_dir):
	"""Create XML that should be passed to pmmcli.exe --import-file-as-dump

	Return XML as a string"""

	return """<?xml version="1.0"?>
<src-dst-files-specification guid="00000000-0000-0000-0000-000000000000" type="server">
	<src>
		<dumps-storage-credentials storage-type="file">
		<root-dir>{source_dir}</root-dir>
		<file-name>{source_file}</file-name>
		</dumps-storage-credentials>
	</src>
	<dst>
		<dumps-storage-credentials storage-type="local">
			<root-dir>{target_dir}</root-dir>
			<ignore-backup-sign>true</ignore-backup-sign>
		</dumps-storage-credentials>
	</dst>
</src-dst-files-specification>
	""".format(
		source_dir=source_dir, 
		source_file=source_file,
		target_dir=target_dir
	)

