from parallels.core import messages
import logging
import os
import ntpath
from xml.etree import ElementTree

from parallels.core.utils import download_zip
from parallels.core.utils import get_thirdparties_base_path
from parallels.core.utils import windows_utils
from parallels.core.utils.common import cached
from parallels.core.utils.common.threading_utils import synchronized_by_args, synchronized, \
	execute_once_synchronized_by_args
from parallels.core.utils.common.xml import elem

logger = logging.getLogger(__name__)


class CopyMailPsaMailBackup(object):
	def __init__(self, get_mail_provider_by_subscription_name, get_rsync):
		self._get_mail_provider_by_subscription_name = get_mail_provider_by_subscription_name
		self._get_rsync = get_rsync

	def copy_mail(self, global_context, migrator_server, subscription, issues):
		for domain in subscription.converted_mail_backup.iter_domains():
			if domain.mailsystem is None:
				logger.debug(messages.SKIP_COPYING_MAIL_CONTENT_FOR_DOMAIN, domain.name)
				continue

			self._copy_domain_mail(subscription, domain)

	def _copy_domain_mail(self, subscription, domain):
		for mailbox in domain.iter_mailboxes():
			self._copy_mailbox(subscription, domain, mailbox)

	def _copy_mailbox(self, subscription, domain, mailbox):
		self._backup_mailbox_content(subscription, domain, mailbox)
		self._restore_mailbox_content(subscription, domain, mailbox)
		self._remove_temporary_files(subscription, domain, mailbox)

	def _backup_mailbox_content(self, subscription, domain, mailbox):
		source_server = subscription.mail_source_server
		mailbox_temp_dir = source_server.get_session_file_path(
			ntpath.join('mail', domain.name.encode('idna'), mailbox.name.encode('idna'))
		)
		with source_server.runner() as runner:
			runner.mkdir(mailbox_temp_dir)

		accounts_file = ntpath.join(mailbox_temp_dir, 'Accounts.xml')
		dump_file = ntpath.join(mailbox_temp_dir, 'Dump.zip')
		log_path = ntpath.join(mailbox_temp_dir, 'MailBackupLogs')

		if source_server.mail_settings.psamailbackup_provider is not None:
			# Mail provider specified in configuration file overrides defaults
			provider = source_server.mail_settings.psamailbackup_provider
		elif self._get_mail_provider_by_subscription_name:
			# Detect mail provider with provided function
			provider = self._get_mail_provider_by_subscription_name(
				subscription.name
			)
		else:
			# Mail provider is not specified - use autodetect
			provider = None

		if provider is not None:
			# Pass selected mail provider to PSAMailBackup
			provider_str = ' --provider=%s' % (provider,)
		else:
			# Mail provider is not specified - use autodetect
			provider_str = ''

		backup_tool = PSAMailBackupDeployer.get_instance().deploy_backup(source_server)

		with source_server.runner() as runner:
			runner.remove_file(dump_file)
			runner.remove_file(accounts_file)
			runner.upload_file_content(
				accounts_file, self._generate_accounts_file(domain, mailbox)
			)

			runner.sh(
				ur'{backup_tool} --dump-file="{dump}" '
				ur'--logpath="{log_path}" '
				ur'--account-file="{accounts_file}"' + provider_str,
				dict(
					backup_tool=backup_tool,
					dump=dump_file,
					accounts_file=accounts_file,
					log_path=log_path,
				)
			)

	def _restore_mailbox_content(self, subscription, domain, mailbox):
		target_server = subscription.mail_target_server
		source_server = subscription.mail_source_server
		mailbox_temp_dir = target_server.get_session_file_path(
			ntpath.join('mail', domain.name.encode('idna'), mailbox.name.encode('idna'))
		)
		with target_server.runner() as runner:
			runner.mkdir(mailbox_temp_dir)

		accounts_file = ntpath.join(mailbox_temp_dir, 'Accounts.xml')
		dump_file = ntpath.join(mailbox_temp_dir, 'Dump.zip')
		log_path = ntpath.join(mailbox_temp_dir, 'MailRestoreLogs')
		restore_tool = PSAMailBackupDeployer.get_instance().deploy_restore(target_server)

		with target_server.runner() as runner:
			runner.remove_file(dump_file)
			runner.remove_file(accounts_file)
			runner.upload_file_content(
				accounts_file, self._generate_accounts_file(domain, mailbox)
			)

		rsync = self._get_rsync(source_server, target_server, subscription.source_mail_ip)
		rsync.sync(
			'migrator/mail/%s/%s/Dump.zip' % (
				domain.name.encode('idna'), mailbox.name.encode('idna')
			),
			windows_utils.convert_path_to_cygwin(dump_file)
		)

		with target_server.runner() as runner:
			runner.sh(
				ur'{restore_tool} --dump-file={dump} '
				ur'--logpath={log_path} '
				ur'--account-file={accounts_file}',
				dict(
					restore_tool=restore_tool,
					dump=dump_file,
					accounts_file=accounts_file,
					log_path=log_path
				)
			)

	def _remove_temporary_files(self, subscription, domain, mailbox):
		target_server = subscription.mail_target_server
		source_server = subscription.mail_source_server

		target_mailbox_temp_dir = target_server.get_session_file_path(
			ntpath.join('mail', domain.name.encode('idna'), mailbox.name.encode('idna'))
		)
		target_accounts_file = ntpath.join(target_mailbox_temp_dir, 'Accounts.xml')
		target_dump_file = ntpath.join(target_mailbox_temp_dir, 'Dump.zip')
		with target_server.runner() as runner:
			runner.remove_file(target_dump_file)
			runner.remove_file(target_accounts_file)

		source_mailbox_temp_dir = source_server.get_session_file_path(
			ntpath.join('mail', domain.name.encode('idna'), mailbox.name.encode('idna'))
		)
		source_accounts_file = ntpath.join(source_mailbox_temp_dir, 'Accounts.xml')
		source_dump_file = ntpath.join(source_mailbox_temp_dir, 'Dump.zip')
		with source_server.runner() as runner:
			runner.remove_file(source_dump_file)
			runner.remove_file(source_accounts_file)

	@staticmethod
	def _generate_accounts_file(domain, mailbox):
		xml_node = elem('Accounts', [
			elem(
				'Domain',
				[elem('Account', attrs={'Name': mailbox.name, 'Pwd': mailbox.password})],
				{'Name': domain.name.encode('idna'), 'StoredName': domain.name.encode('idna')}
			),
		])
		return ElementTree.tostring(xml_node, 'utf-8', 'xml')

	@staticmethod
	def _upload_files(server, files, local_dir):
		with server.runner() as runner:
			for file_name in files:

				local_path = os.path.join(get_thirdparties_base_path(), local_dir, file_name)
				remote_path = server.get_session_file_path(file_name)
				runner.upload_file(local_path, remote_path)


class PSAMailBackupDeployer(object):
	"""Deploy PSAMailBackup/PSAMailRestore files to servers"""

	def __init__(self):
		self._backup_servers = dict()
		self._restore_servers = dict()

	@staticmethod
	@synchronized
	@cached
	def get_instance():
		return PSAMailBackupDeployer()

	@synchronized_by_args
	def deploy_backup(self, server):
		"""Deploy files necessary for making mail backup"""
		if server not in self._backup_servers:
			# deploy once
			logger.debug(messages.LOG_DEPLOY_PSAMAILBACKUP, server.description())
			self._upload_files(server, self._psa_mail_common_files, 'PSAMailcommon')
			remote_dir = server.get_session_file_path(r"locale\en-US")
			with server.runner() as runner:
				runner.mkdir(remote_dir)

			self._upload_files(server, self._psa_locale_files, 'PSAMailcommon')
			self._upload_files(server, self._psa_mail_backup_files, 'PSAMailbackup')
			self._backup_servers[server] = server.get_session_file_path('PSAMailbackup.exe')

		return self._backup_servers[server]

	@synchronized_by_args
	def deploy_restore(self, server):
		"""Deploy files necessary for restoring mail backups"""
		if server not in self._restore_servers:
			# deploy once
			logger.debug(messages.LOG_DEPLOY_PSAMAILRESTORE, server.description())
			self._upload_files(server, self._psa_mail_common_files, 'PSAMailcommon')
			remote_dir = server.get_session_file_path(r"locale\en-US")
			with server.runner() as runner:
				runner.mkdir(remote_dir)
			self._upload_files(server, self._psa_locale_files, 'PSAMailcommon')
			self._upload_files(server, self._psa_mail_restore_files, 'PSAMailrestore')
			self._restore_servers[server] = server.get_session_file_path("PSAMailrestore.exe")

		return self._restore_servers[server]

	def _upload_files(self, server, files, directory):
		store_path = self._download_package(directory)
		with server.runner() as runner:
			for file_name in files:
				local_path = os.path.join(store_path, file_name)
				remote_path = server.get_session_file_path(file_name)
				runner.upload_file(local_path, remote_path)

	@execute_once_synchronized_by_args
	def _download_package(self, package_name):
		store_path = os.path.join(get_thirdparties_base_path(), package_name)
		if not os.path.exists(store_path):
			store_url = 'http://autoinstall.plesk.com/panel-migrator/thirdparties/%s.zip' % package_name
			logger.info(messages.DOWNLOAD_PSAMAIL.format(name=package_name, url=store_url))
			download_zip(store_url, store_path)
		return store_path

	_psa_mail_common_files = [
		"CommunigateProMailsrvprovider.dll", "ForeignMigratorCoreCommon.dll", "ForeignMigratorCoreInterfaces.dll",
		"ForeignMigratorEngineCore.dll", "hMail3mailsrvprovider.dll", "hMail4mailsrvprovider.dll",
		"hMailmailsrvprovider.dll", "ICSharpCode.SharpZipLib.dll", "IMailmailsrvprovider.dll", "Legacy.dll",
		"libmariadb.dll", "MailEnablemailsrvprovider.dll", "MailMsgMigrMng.dll", "MDaemonmailsrvprovider.dll",
		"Merakmailsrvprovider.dll", "msvcr110.dll", "MySqlClient.dll", "psabackupcommon.dll", "psacommon.dll",
		"psacontentfile.dll", "PsaFactory110.dll", "PsaFactory115.dll", "PsaFactory120.dll", "PsaFactory83.dll",
		"PsaFactory90.dll",	"PsaFactory.dll", "PsaMailBackupRestoreCommon.dll", "SmarterMailmailsrvprovider.dll",
		"TCPmailsrvprovider.dll", "zlib1.dll"

	]

	_psa_locale_files = [
		"locale/en-US/CrossSysAgentSup.xml", "locale/en-US/DbMigrator.xml", "locale/en-US/DNSMigrator.xml",
		"locale/en-US/FtpMigrator.xml", "locale/en-US/MailMigrator.xml", "locale/en-US/migrmng.xml",
		"locale/en-US/PsaMigrMng90.xml", "locale/en-US/WINAgentMng.xml", "locale/en-US/WinAgent.xml"
	]

	_psa_mail_backup_files = [
		"PSAMailbackup.exe", "PSAMailbackup.exe.config"
	]

	_psa_mail_restore_files = [
		"PSAMailrestore.exe", "PSAMailrestore.exe.config"
	]
