import os
import ntpath

import logging

import xml.etree.ElementTree as et

from parallels.utils import is_run_on_windows
from parallels.common.utils.yaml_utils import read_yaml, write_yaml
from parallels.common.utils.pmm.agent import PmmMigrationAgentBase, DumpAll, DumpSelected
from parallels.common.utils import windows_utils
from parallels.common.utils.windows_utils import path_join as windows_path_join, get_from_registry

from parallels.common.utils import migrator_utils
import parallels.plesks_migrator

logger = logging.getLogger(__name__)


class WindowsPmmMigrationAgent(PmmMigrationAgentBase):
	"""Abstract class for Windows migration agents (Plesk, Helm, Expand)."""

	def _run(self, local_dump_path, local_log_filename, selection=DumpAll()):
		"""Create migration dump and download it to session directory."""
		logger.info("Creating migration dump '%s'." % local_dump_path)
		self.local_log_filename = local_log_filename
		with self._source_server.runner() as runner:
			dump_path = self._make_dump(runner, selection)
			self._download_dump(dump_path, local_dump_path, runner)

	def _run_shallow(self, local_dump_path, local_log_filename):
		"""Create a dump with simplified structure, useful for creation of migration list."""
		logger.info("Create shallow migration dump '%s'." % local_dump_path)
		self.local_log_filename = local_log_filename
		with self._source_server.runner() as runner:
			file_path = self._make_shallow_dump(runner)
			runner.get_file(file_path, local_dump_path)

	def _make_dump(self, runner, selection):
		raise NotImplementedError()

	def _make_shallow_dump(self, runner):
		raise NotImplementedError()

	def _get_create_dump_command(self, runner, selection=DumpAll()):
		if isinstance(selection, DumpSelected):
			migration_objects_list = et.Element("migration-objects-list")

			resellers = et.SubElement(migration_objects_list, "resellers")
			for reseller in selection.resellers:
				et.SubElement(resellers, "reseller").text = reseller
			clients = et.SubElement(migration_objects_list, "clients")
			for client in selection.clients:
				et.SubElement(clients, "client").text = client
			domains = et.SubElement(migration_objects_list, "domains")
			for domain in selection.domains:
				et.SubElement(domains, "domain").text = domain

			filter_filename = self._source_server.get_session_file_path("filter.xml")
			runner.upload_file_content(
				filter_filename,
				et.tostring(migration_objects_list, 'utf-8', 'xml')
			)

			options = {
				'agent_script': self._get_agent_path(),
				'dump_command': '--create-full-migration-dump',
				'filter_file' : filter_filename
			}
			command = ur'cmd.exe /C "{agent_script} {dump_command} {filter_file}"'
		else:
			options = {
				'agent_script': self._get_agent_path(),
				'dump_command': '--create-full-migration-dump',
			}
			command = ur'cmd.exe /C "{agent_script} {dump_command}"'

		return command, options

	def _get_create_shallow_dump_command(self):
		options = {
			'agent_script': self._get_agent_path(),
			'dump_command': '--create-shallow-migration-dump',
		}
		command = ur'cmd.exe /C "{agent_script} {dump_command}"'

		return command, options

	def _get_remote_dump_path(self, agent_stdout):
		"""Parse agent stdout, return the directory, where migration dump is stored."""
		return agent_stdout.strip('\r\n"')

	def _download_dump(self, remote_dump_path, local_dump_path, runner):
		"""Download migration dump files as a ZIP archive with specified name."""
		logger.info("Downloading migration dump '%s'" % (remote_dump_path,))
		is_zipfile = remote_dump_path[-4:] == '.zip'
		if is_zipfile:
			zipfilename = remote_dump_path
		else:
			zipfilename = "%s.zip" % remote_dump_path
			self.create_zip(runner, remote_dump_path, zipfilename)
		runner.get_file(zipfilename, local_dump_path)

	def create_zip(self, runner, directory, zipfilename):
		"""Remotely create a ZIP archive, that contains the specified directory."""
		zip_script = 'zipfolder.vbs'
		zipscript_local_path = migrator_utils.get_package_extras_file_path(
				parallels.plesks_migrator, zip_script)
		zipscript_remote_path = self._source_server.get_session_file_path(zip_script)
		runner.upload_file(zipscript_local_path, zipscript_remote_path)

		zipoptions = {
			'ziptool': zipscript_remote_path,
			'directory': directory,
			'zipfile': zipfilename
		}
		runner.sh(ur'cmd.exe /C "cscript {ziptool} {directory} {zipfile}"', zipoptions)
		runner.remove_file(zipscript_remote_path)

	def _get_agent_path(self):
		return ntpath.join(self._get_base_path(), 'WINAgentMng.exe')

	def _deploy(self):
		if self._get_base_path() is None:
			with self._source_server.runner() as runner:
				# upload installer
				installer_filename = self._get_installer_filename()
				installer_path = self._source_server.get_session_file_path(installer_filename)
				runner.upload_file(self._get_installer_path_in_storage(), installer_path)

				possible_registry_keys = [
					'HKLM\SOFTWARE\Wow6432Node\PanelMigrator\Agent',
					'HKLM\SOFTWARE\PanelMigrator\Agent'
				]

				# check if agent was already installed and remove it
				base_path = get_from_registry(runner, possible_registry_keys, 'INSTALLDIR')
				if base_path is not None:
					runner.sh_unchecked('msiexec /x "%s" /qn' % installer_path)

				# install agent and store path in session
				runner.sh('msiexec /i "%s" /qn' % installer_path)
				base_path = get_from_registry(runner, possible_registry_keys, 'INSTALLDIR')
				if base_path is not None:
					write_yaml(
						self.global_context.session_files.get_path_to_migration_agent(self._source_server.ip()),
						base_path
					)

	def _get_base_path(self):
		return read_yaml(
			self.global_context.session_files.get_path_to_migration_agent(self._source_server.ip()),
			True
		)

	def _get_installer_path_in_storage(self):
		installer_filename = self._get_installer_filename()
		if is_run_on_windows():
			path_parts = [os.path.dirname(__file__)] + [r'..'] * 7 + ['agents', installer_filename]
			return os.path.realpath(os.path.join(*path_parts))
		else:
			return '/opt/panel-migrator/agents/%s' % installer_filename

	def _get_installer_filename(self):
		return 'panel-migrator-agent-12.0.msi'


class WindowsPleskPmmMigrationAgent(WindowsPmmMigrationAgent):
	"""Plesk migration agent for Windows."""

	def _make_dump(self, runner, selection=DumpAll()):
		"""Run agent on source server, generate migration dump."""
		command, options = self._get_create_dump_command(runner, selection)
		result = runner.sh(command, options)
		dump_path = self._get_remote_dump_path(result)
		if not self._source_server.plesk_major_version >= 9:
			dump_path = self._convert_dump_to_plesk9(dump_path, runner)
		return dump_path

	def _make_shallow_dump(self, runner):
		"""Run agent on source server, generate shallow migration dump."""
		command, options = self._get_create_shallow_dump_command()
		result = runner.sh(command, options)
		return self._get_remote_dump_path(result)

	def _convert_dump_to_plesk9(self, pp8_dump_path, runner):
		"""Convert Windows backup format remotely (on the source server)."""
		converter_path = self.upload_plesk9_converter(runner)
		logger.info(u"Convert Plesk 8 dump to Plesk 9 format.")

		# TODO workaround for issue https://jira.sw.ru/browse/PMT-436
		# make path to backup file as short as possible, but root cause, as we suppose,
		# should be fixed in plesk 8 backup converter
		source_dump_path = self._source_server.get_session_file_path('s')
		runner.sh(
			ur'cmd.exe /C "move /Y {pp8_dump_path} {source_dump_path}',
			dict(
				pp8_dump_path=pp8_dump_path,
				source_dump_path=source_dump_path
			)
		)

		converted_dump_path = self._source_server.get_session_file_path('c')
		runner.sh(
			ur'cmd.exe /C "IF EXIST {tmpdir} (rmdir /Q /S {tmpdir})"',
			dict(tmpdir=converted_dump_path))
		runner.sh(
			ur'cmd.exe /C "{converter_path} --source={source_dump_path}'
			' --destination={converted_dump_path}"',
			dict(
				converter_path=converter_path,
				source_dump_path=source_dump_path,
				converted_dump_path=converted_dump_path
			)
		)
		return converted_dump_path

	def upload_plesk9_converter(self, runner):
		"""Upload Plesk dump format converter to the source server."""
		source_plesk_dir = self._source_server.plesk_dir
		runner.mkdir(ur'%s\PMM' % (source_plesk_dir,))
		converter = ur'admin/bin/pre9-backup-convert.exe'
		converter_xsl = ur'PMM/migration-dump-convert.xsl'
		for path in [converter, converter_xsl]:
			local_path = migrator_utils.get_package_extras_file_path(
				parallels.plesks_migrator,
				os.path.basename(path)
			)
			remote_path = os.path.join(source_plesk_dir.rstrip('\\'), path)
			runner.upload_file(local_path, remote_path)

		upload_path = windows_path_join(source_plesk_dir, windows_utils.to_cmd(converter))
		logger.debug("Uploaded converter to '%s'" % upload_path)
		return upload_path
