from parallels.core import safe_format
from parallels.core.actions.base.subscription_action import SubscriptionAction
from parallels.core.actions.utils.multithreading_properties import MultithreadingProperties
from parallels.core.content.mail.imap import CopyMailIMAP
from parallels.core.content.mail.local_dir import CopyMailLocalDir
from parallels.core.content.mail.mail_migrator import CopyMailMailMigrator, AdditionalMailServerOptions
from parallels.core.content.mail.rsync import CopyMailRsync
from parallels.core.logging import get_logger
from parallels.core.migrator_config import MailContent
from parallels.core.reports.model.issue import Issue
from parallels.core.utils.common import ilen
from parallels.core.utils.migrator_utils import get_optional_option_value
from parallels.core.utils.paths.copy_web_content import CopyContentItem
from parallels.core.utils.paths.mail_paths import MailAbsolutePath, MailboxDirectory, MailboxMaildirDirectory, \
    MailboxTemplatePath
from parallels.plesk.hosting_description.model import CopyMessagesMode, PasswordType
from parallels.plesk.source.custom import messages

logger = get_logger(__name__)


class CopyMailMessages(SubscriptionAction):
    """Copy mail messages from custom panel's server - either with rsync (for Unix) or MailMigrator.exe (for Windows)"""

    def get_description(self):
        """Get short description of action as string

        :rtype: str | unicode
        """
        return messages.ACTION_COPY_MAIL_MESSAGES

    def get_failure_message(self, global_context, subscription):
        """Get message for situation when action failed

        :type global_context: parallels.common.global_context.GlobalMigrationContext
        :type subscription: parallels.common.migrated_subscription.MigratedSubscription
        :rtype: str | unicode
        """
        return messages.ACTION_COPY_MAIL_MESSAGES_FAILURE

    def is_critical(self):
        """If action is critical or not

        If action is critical and it failed for a subscription, migration tool
        won't run the next operations for the subscription.

        :rtype: bool
        """
        return False

    def get_multithreading_properties(self):
        """Get how multithreading should be applied for that action

        :rtype: parallels.core.actions.utils.multithreading_properties.MultithreadingProperties
        """
        return MultithreadingProperties(can_use_threads=True, use_threads_by_default=True)

    def filter_subscription(self, global_context, subscription):
        """Check if we should run this action on given subscription or not

        :type global_context: parallels.plesk.source.custom.global_context.CustomPanelGlobalMigrationContext
        :type subscription: parallels.plesk.source.custom.migrated_subscription.CustomPanelMigratedSubscription
        """
        if subscription.hosting_description is None:
            return False

        cli_skip_copy_mail_content = get_optional_option_value(
            global_context.options, 'skip_copy_mail_content', False
        )
        if cli_skip_copy_mail_content:
            return False

        for mailbox in subscription.hosting_description.iter_mailboxes():
            if self._get_mailbox_copy_messages_mode(subscription, mailbox) != CopyMessagesMode.NONE:
                return True

        return False

    def run(self, global_context, subscription):
        """Run action on given subscription

        :type global_context: parallels.plesk.source.custom.global_context.CustomPanelGlobalMigrationContext
        :type subscription: parallels.plesk.source.custom.migrated_subscription.CustomPanelMigratedSubscription
        """
        copy_messages_modes = {}
        for mailbox in subscription.hosting_description.iter_mailboxes():
            copy_messages_modes[mailbox.name] = self._get_mailbox_copy_messages_mode(subscription, mailbox)

        issues = []

        used_rsync = False

        for domain in subscription.mail_converted_dump.iter_domains():
            for mailbox in domain.iter_mailboxes():
                try:
                    hd_mailbox = subscription.hosting_description.get_mailbox(mailbox.full_name)
                    copy_messages_mode = copy_messages_modes[mailbox.full_name]
                    if copy_messages_mode != CopyMessagesMode.NONE:
                        logger.finfo(messages.COPY_MESSAGES_OF_MAILBOX, mailbox=mailbox.full_name)

                    if copy_messages_mode == CopyMessagesMode.REMOTE_DIRECTORY:
                        used_rsync |= self._copy_mail_rsync(global_context, hd_mailbox, mailbox, subscription)
                    elif copy_messages_mode == CopyMessagesMode.LOCAL_DIRECTORY:
                        if hd_mailbox.source_directory is not None:
                            logger.fdebug(messages.COPY_MAILBOX_FROM_LOCAL_DIR, mailbox=mailbox.full_name)
                            copy_mail = CopyMailLocalDir()
                            copy_mail.copy_mailbox(subscription, mailbox, hd_mailbox.source_directory)
                        else:
                            logger.fdebug(messages.SKIP_COPY_MAILBOX_FROM_LOCAL_DIR, mailbox=mailbox.full_name)
                    elif copy_messages_mode == CopyMessagesMode.MAIL_MIGRATOR:
                        logger.fdebug(messages.COPY_MAILBOX_WITH_MAIL_MIGRATOR, mailbox=mailbox.full_name)
                        hosting_description = global_context.get_hosting_description(subscription.model.source)
                        default_mail_server = hosting_description.get_default_mail_server()
                        source_mail_server_options = None
                        if default_mail_server is not None:
                            source_mail_server_options = AdditionalMailServerOptions(
                                default_mail_server.server_type,
                                default_mail_server.administrator.username,
                                default_mail_server.administrator.password
                            )
                        copy_mail = CopyMailMailMigrator()
                        copy_mail.copy_mailbox(
                            subscription, domain, mailbox, issues,
                            hd_mailbox.source_short_name,
                            hd_mailbox.source_domain_name,
                            hd_mailbox.source_password,
                            source_mail_server_options
                        )
                    elif copy_messages_mode == CopyMessagesMode.IMAP:
                        logger.fdebug(messages.COPY_MAILBOX_WITH_IMAP, mailbox=mailbox.full_name)
                        copy_mail = CopyMailIMAP()
                        mailbox_ip = self._get_mailbox_ip(subscription, hd_mailbox)
                        copy_mail.copy_mailbox(
                            subscription, domain, mailbox, mailbox_ip,
                            hd_mailbox.source_short_name, hd_mailbox.source_domain_name,
                            hd_mailbox.source_password
                        )
                    elif copy_messages_mode == CopyMessagesMode.NONE:
                        logger.fdebug(messages.COPY_MAILBOX_NONE, mailbox=mailbox.full_name)
                    else:
                        logger.fwarn(messages.COPY_MAILBOX_UNKNOWN, mailbox=mailbox.full_name, mode=copy_messages_mode)
                except Exception as e:
                    logger.exception()
                    logger.ferror(messages.FAILED_TO_COPY_MAILBOX, mailbox=mailbox.full_name, reason=e)
                    issues.append(Issue(
                        'failed_to_copy_mailbox', Issue.SEVERITY_ERROR,
                        safe_format(messages.FAILED_TO_COPY_MAILBOX, mailbox=mailbox.full_name, reason=e),
                    ))

        if used_rsync:
            copy_mail = CopyMailRsync(None)
            copy_mail.update_panel_usage_stats(subscription)

        for issue in issues:
            global_context.safe.add_issue_subscription(subscription.name, issue)

    @staticmethod
    def _copy_mail_rsync(global_context, hd_mailbox, mailbox, subscription):
        """Copy mail messages with rsync (Unix only). Return if any file was copied.

        :type global_context: parallels.plesk.source.custom.global_context.CustomPanelGlobalMigrationContext
        :type subscription: parallels.plesk.source.custom.migrated_subscription.CustomPanelMigratedSubscription
        :type hd_mailbox: parallels.plesk.hosting_description.model.HostingDescriptionMailbox
        :param mailbox:
        :rtype: bool
        """
        tocopy = []

        if hd_mailbox.source_directory is not None:
            tocopy.append(CopyContentItem(
                source_path=MailAbsolutePath(
                    hd_mailbox.source_directory
                    if isinstance(hd_mailbox.source_directory, basestring)
                    else hd_mailbox.source_directory['path']
                ),
                target_path=MailboxDirectory(mailbox_name=mailbox.name),
                exclude=CopyMailRsync.DEFAULT_MAIL_USER_DIR_EXCLUDES + (
                    hd_mailbox.source_directory.get('exclude', [])
                    if isinstance(hd_mailbox.source_directory, dict)
                    else []
                )
            ))
        if hd_mailbox.source_maildir_directory is not None:
            tocopy.append(CopyContentItem(
                source_path=MailAbsolutePath(
                    hd_mailbox.source_maildir_directory
                    if isinstance(hd_mailbox.source_maildir_directory, basestring)
                    else hd_mailbox.source_maildir_directory['path']
                ),
                target_path=MailboxMaildirDirectory(mailbox_name=mailbox.name),
                exclude=(
                    hd_mailbox.source_maildir_directory.get('exclude', [])
                    if isinstance(hd_mailbox.source_maildir_directory, dict)
                    else []
                )
            ))
        for mail_file_info in hd_mailbox.iter_mail_files():
            tocopy.append(CopyContentItem(
                source_path=MailAbsolutePath(mail_file_info.source),
                target_path=MailboxTemplatePath(
                    mailbox_name=mailbox.name, template=mail_file_info.target,
                ),
                exclude=mail_file_info.exclude,
                skip_if_source_not_exists=mail_file_info.skip_if_source_not_exists
            ))

        if len(tocopy) > 0:
            logger.fdebug(messages.COPY_MAILBOX_WITH_RSYNC, mailbox=mailbox.full_name)
            copy_mail = CopyMailRsync(None)
            copy_mail.copy_mailbox_directories(
                global_context, subscription, mailbox, tocopy
            )
            return True
        else:
            logger.fdebug(messages.SKIP_COPY_MAILBOX_WITH_RSYNC, mailbox=mailbox.full_name)
            return False

    @staticmethod
    def _get_mailbox_ip(subscription, mailbox):
        """
        :type subscription: parallels.plesk.source.custom.migrated_subscription.CustomPanelMigratedSubscription
        :type mailbox: parallels.plesk.hosting_description.model.HostingDescriptionMailbox
        """
        mail_service = subscription.hosting_description.mail_service

        if mailbox.mail_ip is not None:
            return mailbox.mail_ip
        elif mailbox.mail_ipv6 is not None:
            return mailbox.mail_ipv6
        elif mail_service is not None and mail_service.mail_ip is not None:
            return mail_service.mail_ip
        elif mail_service is not None and mail_service.mail_ipv6 is not None:
            return mail_service.mail_ipv6
        elif subscription.mail_source_server is not None:
            return subscription.mail_source_server.ip()
        else:
            return None

    @staticmethod
    def _get_mailbox_copy_messages_mode(subscription, mailbox):
        """
        :type mailbox: parallels.plesk.hosting_description.model.HostingDescriptionMailbox
        :type subscription: parallels.plesk.source.custom.migrated_subscription.CustomPanelMigratedSubscription
        """
        mail_service = subscription.hosting_description.mail_service
        if mail_service is not None:
            subscription_copy_messages_mode = mail_service.copy_messages
        else:
            subscription_copy_messages_mode = CopyMessagesMode.AUTO

        if mailbox.copy_messages is not None:
            mailbox_copy_messages_mode = mailbox.copy_messages
        else:
            mailbox_copy_messages_mode = subscription_copy_messages_mode

        if subscription.mail_source_server is not None:
            if subscription.mail_source_server.mail_settings.mode == MailContent.NONE:
                mailbox_copy_messages_mode = CopyMessagesMode.NONE
            else:
                if mailbox_copy_messages_mode == CopyMessagesMode.AUTO:
                    if subscription.mail_source_server.is_windows():
                        # We have direct access to the server, and server is Windows => use MailMigrator
                        mailbox_copy_messages_mode = CopyMessagesMode.MAIL_MIGRATOR
                    else:
                        if (
                            mailbox.source_directory is not None or
                            mailbox.source_maildir_directory is not None or
                            ilen(mailbox.iter_mail_files()) > 0
                        ):
                            # We have direct access to the server, server is Linux,
                            # and mailbox directory is specified => copy messages from directory with rsync
                            mailbox_copy_messages_mode = CopyMessagesMode.REMOTE_DIRECTORY
                        elif (
                            (mailbox.password_type == PasswordType.PLAIN and mailbox.password is not None) or
                            mailbox.source_password is not None
                        ):
                            # Mailbox directory was not specified, but we have plain password => copy with IMAP
                            mailbox_copy_messages_mode = CopyMessagesMode.IMAP
                        else:
                            # No direct access, no password => can not copy mail messages
                            mailbox_copy_messages_mode = CopyMessagesMode.NONE
        else:
            migration_disabled_for_source = (
                subscription.hosting_description_config is not None and
                subscription.hosting_description_config.mail_settings is not None and
                subscription.hosting_description_config.mail_settings.mode == MailContent.NONE
            )
            if migration_disabled_for_source:
                mailbox_copy_messages_mode = CopyMessagesMode.NONE
            else:
                if mailbox_copy_messages_mode == CopyMessagesMode.AUTO:
                    # We don't have direct access to the server
                    if mailbox.source_directory is not None and not subscription.mail_target_server.is_windows():
                        # Messages were already copied to the target server, now we should place them to correct place.
                        mailbox_copy_messages_mode = CopyMessagesMode.LOCAL_DIRECTORY
                    elif (
                        (mailbox.password_type == PasswordType.PLAIN and mailbox.password is not None) or
                        mailbox.source_password is not None
                    ):
                        # If we have plain-text password - try to copy mail messages with IMAP
                        mailbox_copy_messages_mode = CopyMessagesMode.IMAP
                    else:
                        mailbox_copy_messages_mode = CopyMessagesMode.NONE

        return mailbox_copy_messages_mode
