import os
import re
import posixpath

from parallels.common.content.mail.rsync import CopyMailRsync
from parallels.common.content.mail.rsync import SourceMailDirectory
from parallels.common.content.mail.rsync import TargetMailboxDirectory

class ConfixxCopyMailContentSourceDirectory(SourceMailDirectory):
	def __init__(
		self, subscription, domain,
		confixx_configuration, agent_home, mailmapping_tool
	):
		self.domain = domain
		self.subscription = subscription
		self.confixx_configuration = confixx_configuration
		self.agent_home = agent_home
		self.mailmapping_tool = mailmapping_tool
		self.temporary_directory = None

	def prepare(self):
		source_server = self.subscription.mail_source_server

		self.temporary_directory = None
		if self.confixx_configuration.is_maildir_used():
			# copy maildir content as-is
			maildir_pair = self._get_maildir_mapping(
				self.domain, self.subscription
			)
		else:
			# Run 'mailbox -> maildir' conversion on source server
			self.temporary_directory = source_server.get_session_file_path(
				posixpath.join('mail', self.domain.name)
			) 
			with source_server.runner() as runner_source:
				runner_source.mkdir(self.temporary_directory)
			maildir_pair = self._get_mailbox_maildir_mapping(
				self.subscription, self.domain, self.temporary_directory
			)
		return list(maildir_pair)

	def cleanup(self):
		source_server = self.subscription.mail_source_server
		if self.temporary_directory is not None:
			with source_server.runner() as runner:
				runner.sh(
					ur'rm -rf {directory}',
					{'directory': self.temporary_directory}
				)

	def _get_maildir_mapping(self, domain, subscription):
		"""For each maildir, return a pair of source, destination paths."""
		# for maildir, use the path
		# getValue("pop_homeDir") . "/$mboxName/" . ConfixxConfig::getValue('mailBoxName')
		confixx_mailhome = self.confixx_configuration._get_config_var('pop_homeDir')
		confixx_maildirname = self.confixx_configuration._get_config_var('mailBoxName')
		for mailbox, user_maildir in self._get_domain_mailboxes(subscription, domain):
			source_maildir = posixpath.join(
				confixx_mailhome, user_maildir, confixx_maildirname
			)
			yield (source_maildir, TargetMailboxDirectory(mailbox.name))

	def _get_mailbox_maildir_mapping(self, subscription, domain, maildir_home):
		"""Convert mailboxes to maildir; put them in a temporary directory.
		
		Return a pair of source, destination paths.
		"""
		domain_mailboxes = self._get_domain_mailboxes(subscription, domain)
		for mailbox, mailbox_filename in domain_mailboxes:
			source_maildir = self._mailbox_to_maildir(
				subscription.mail_source_server, mailbox, mailbox_filename, 
				maildir_home
			)
			yield (source_maildir, TargetMailboxDirectory(mailbox.name))
	
	def _mailbox_to_maildir(
		self, source_server, mailbox, mailbox_filename, maildir_home
	):
		"""Convert mailbox to maildir; return path to the generated Maildir."""
		mailbox_path = self.confixx_configuration.get_mailbox_path(
			mailbox_filename
		)
		maildir = posixpath.join(maildir_home, mailbox.name)
		with source_server.runner() as runner_source:
			runner_source.mkdir(maildir)
			runner_source.sh(
				ur'cd {mbox2maildir_path}; '
				ur'perl mb2md.pl -s {mailbox} -d {maildir}',
				{
					'mbox2maildir_path': os.path.join(
						self.agent_home, 'shared_legacy'
					),
					'mailbox': mailbox_path,
					'maildir': posixpath.join(maildir, 'Maildir')
			})
		return maildir

	def _get_domain_mailboxes(self, subscription, domain):
		"""Iterate over domain mail users; return Mailbox objects.
		
		Skip mail users that have no mailboxes associated.
		Note: 'Mailbox' object also represents Maildir.
		"""
		mailmap = self._get_subscription_mailmapping(subscription)
		for mailbox in domain.iter_mailboxes():
			if mailbox.full_name in mailmap:
				filename = mailmap[mailbox.full_name]
				yield (mailbox, filename)

	def _get_subscription_mailmapping(self, subscription):
		"""Convert Confixx mail model to a [simpler] Plesk one.

		In Confixx, email and mailbox are two separate entities. In particular,
		there can be mailboxes without email and mailboxes with more than one
		email address associated with them.

		To be able to transfer that structure to Plesk, mail box should be
		associated with one email. This mapping is done by the migration agent
		on source panel side.

		Return a dict with '{<email>: <mailbox filename>, ...}' mapping.
		Emails without mailboxes (forwarding), are not included in the list.
		"""
		source_server = subscription.mail_source_server
		with source_server.runner() as runner_source:
			mailmap_str = runner_source.sh(
					ur' cd {agent_home}; \
						echo {client_name} \
						| perl -Ishared_legacy {mailmapping_tool}', {
					'agent_home': self.agent_home,
					'mailmapping_tool': self.mailmapping_tool,
					'client_name': subscription.raw_backup.owner_login
			})

		mailmap = {}
		for line in mailmap_str.split('\n'):
			rx = re.match('^(\S+@\S+)\s+(\S+)$', line)
			if rx:
				(email, mailbox) = rx.groups()
				mailmap[email] = mailbox
		return mailmap

class ConfixxCopyMailContent(CopyMailRsync):

	"""Transfer mail content from Confixx to Plesk server
	
	Confixx mail content format can be Maildir or mailbox. In case of
	Maildir, the content is transferred as-is. Mailboxes are converted to
	Maildirs on Confixx side before transfer is started.
	"""

	def __init__(self, confixx_configuration, agent_home, mailmapping_tool):
		class SourceDirectory(ConfixxCopyMailContentSourceDirectory):
			def __init__(self, subscription, domain):
				super(SourceDirectory, self).__init__(
					subscription, domain, 
					confixx_configuration, agent_home, mailmapping_tool
				)

		super(ConfixxCopyMailContent, self).__init__(SourceDirectory)
