import os
import re
import posixpath

from parallels.core.content.mail.rsync import CopyMailRsync
from parallels.core.content.mail.rsync import SourceMailDirectory
from parallels.core.runners.base import BaseRunner
from parallels.core.utils.paths.mail_paths import MailboxDirectory


class ConfixxCopyMailContentSourceDirectory(SourceMailDirectory):
    def __init__(self, subscription, domain, confixx_configuration, agent_dir, mailmapping_tool):
        super(ConfixxCopyMailContentSourceDirectory, self).__init__(subscription, domain)
        self.confixx_configuration = confixx_configuration
        self.agent_dir = agent_dir
        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:
                assert isinstance(runner, BaseRunner)
                runner.execute_command(
                    u'rm -rf {directory}',
                    dict(directory=self.temporary_directory)
                )

    def _get_maildir_mapping(self, domain, subscription):
        """For each maildir, return a pair of source, destination paths."""
        confixx_mailhome = self.confixx_configuration.get_config_var('pop_homeDir')
        for mailbox, user_maildir in self._get_domain_mailboxes(subscription, domain):
            source_maildir = posixpath.join(confixx_mailhome, user_maildir)
            yield (source_maildir, MailboxDirectory(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, MailboxDirectory(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:
            assert isinstance(runner_source, BaseRunner)
            runner_source.mkdir(maildir)
            runner_source.execute_command(
                u'cd {mbox2maildir_path}; '
                u'perl mb2md.pl -s {mailbox} -d {maildir}',
                dict(
                    mbox2maildir_path=os.path.join(
                        self.agent_dir, '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:
            assert isinstance(runner_source, BaseRunner)
            mailmap_str = runner_source.execute_command(
                u'cd {agent_dir}; echo {client_name} | perl {mailmapping_tool}',
                dict(
                    agent_dir=self.agent_dir,
                    mailmapping_tool=self.mailmapping_tool,
                    client_name=subscription.raw_dump.owner_login
                )
            ).stdout

        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_dir, mailmapping_tool):
        class SourceDirectory(ConfixxCopyMailContentSourceDirectory):
            def __init__(self, subscription, domain):
                super(SourceDirectory, self).__init__(
                    subscription, domain, 
                    confixx_configuration, agent_dir, mailmapping_tool
                )

        super(ConfixxCopyMailContent, self).__init__(SourceDirectory)
