import json

from parallels.core.hosting_repository.mail_account import CreateMailAccountEntity
from parallels.core.logging import get_logger
from parallels.core.utils.common import is_empty, generate_random_password, is_run_on_windows, safe_string_repr
from parallels.plesk.source.mail import messages
from parallels.plesk.source.mail.actions.base.mail_migration import MailMigrationAction
from parallels.plesk.source.mail.exceptions import MailMigratorError
from parallels.plesk.source.mail.status_storage import StatusStorage
from parallels.plesk.source.mail.utils import load_mail_migration_json_unencrypted

logger = get_logger(__name__)


class DeployMailboxes(MailMigrationAction):
    """Read site migration list - with selected applications, files and databases to migrate"""

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

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

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

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :rtype: str | unicode
        """
        return messages.ACTION_DEPLOY_MAILBOXES_FAILURE

    def get_error_id(self):
        """Get error ID for errors generated inside of the current action.

        :rtype: str | unicode
        """
        return 'create-target-mailbox'

    def run(self, global_context):
        """Run action

        :type global_context: parallels.plesk.source.mail.global_context.MailGlobalMigrationContext
        :rtype: None
        """
        mail_address_pair = global_context.migrated_mail_address_pair

        if global_context.status_storage.get_reporting_mode() == StatusStorage.REPORTING_MODE_SYNC:
            global_context.status_storage.set_task_step(StatusStorage.STEP_PREPARE_TARGET)

        is_mailbox_enabled = global_context.hosting_repository.mail_account.is_mailbox_enabled(
            global_context.target_subscription_name,
            mail_address_pair.target_mailname, mail_address_pair.target_domain
        )

        if is_mailbox_enabled is None:  # mailname does not exists - create it with enabled mailbox
            logger.info(messages.CREATE_MAILNAME)
            if global_context.status_storage.get_reporting_mode() == StatusStorage.REPORTING_MODE_SYNC:
                global_context.status_storage.set_task_step(StatusStorage.STEP_CREATE_TARGET_MAILBOX)
            mail_address_pair.target_password = self._set_new_target_password(mail_address_pair)
            self._create_mail_account(global_context, mail_address_pair)
        else:  # mailname exists
            if is_mailbox_enabled:
                if is_run_on_windows():
                    logger.info(messages.MAILBOX_EXISTS_GET_PASSWORD)
                    # Mail migrator on Windows could require password of target mailbox:
                    # for MailEnable and Merak it uses it to connect to the target mailbox by IMAP
                    # to perform finalization of restore.
                    #
                    # Here we retrieve password of the target mailbox from Plesk database and decrypt it.
                    password, password_type = global_context.hosting_repository.mail_account.get_password(
                        global_context.target_subscription_name,
                        mail_address_pair.target_mailname, mail_address_pair.target_domain
                    )
                    if password_type == 'sym':
                        data = load_mail_migration_json_unencrypted(json.dumps({'target_password': password}))
                        mail_address_pair.target_password = data['target_password']
                    elif password_type == 'plain':
                        mail_address_pair.target_password = password
                else:
                    # Mail migrator on Linux does not need password of the target mailbox: it simply
                    # works with files and directories.
                    logger.info(messages.MAILBOX_EXISTS_DO_NOTHING)
            else:
                try:
                    logger.info(messages.MAILNAME_WITHOUT_MAILBOX)
                    if global_context.status_storage.get_reporting_mode() == StatusStorage.REPORTING_MODE_SYNC:
                        global_context.status_storage.set_task_step(StatusStorage.STEP_CREATE_TARGET_MAILBOX)
                    mail_address_pair.target_password = self._set_new_target_password(mail_address_pair)
                    self._enable_mailbox(global_context, mail_address_pair)
                except Exception as e:
                    logger.exception()
                    raise MailMigratorError(
                        'enable-target-mailbox', messages.FAILED_TO_ENABLE_MAILBOX,
                        error_details=safe_string_repr(e)
                    )

    @staticmethod
    def _create_mail_account(global_context, mail_address_pair):
        """
        :type global_context: parallels.plesk.source.mail.global_context.MailGlobalMigrationContext
        :type mail_address_pair: parallels.plesk.source.mail.migrated_mail_address_pair.MigratedMailAddressPair
        :rtype: None
        """
        global_context.hosting_repository.mail_account.create(
            CreateMailAccountEntity(
                mail_address_pair.target_email, mail_address_pair.target_password
            ),
            global_context.target_subscription_name
        )

    @staticmethod
    def _enable_mailbox(global_context, mail_address_pair):
        """
        :type global_context: parallels.plesk.source.mail.global_context.MailGlobalMigrationContext
        :type mail_address_pair: parallels.plesk.source.mail.migrated_mail_address_pair.MigratedMailAddressPair
        :rtype: None
        """
        global_context.hosting_repository.mail_account.enable_mailbox(
            global_context.target_subscription_name,
            mail_address_pair.target_mailname, mail_address_pair.target_domain, mail_address_pair.target_password
        )

    @staticmethod
    def _set_new_target_password(mail_address_pair):
        """
        :type mail_address_pair: parallels.plesk.source.mail.migrated_mail_address_pair.MigratedMailAddressPair
        :rtype: str | unicode
        """
        password = mail_address_pair.new_target_password
        if is_empty(password):
            password = generate_random_password()
        return password
