import re

from parallels.core import MigrationNoContextError, safe_format
from parallels.core.utils.entity import Entity
from parallels.core.utils.safe_structures import safe_get_list, safe_dict
from parallels.plesk.source.mail import messages
from parallels.plesk.source.mail.utils import load_mail_migration_json_unencrypted_from_file
from parallels.plesk.source.mail.utils import load_mail_migration_json_encrypted_from_file


class MailMigrationList(Entity):
    def __init__(self, mail_address_pairs):
        """
        :type mail_address_pairs: list[parallels.plesk.source.mail.mail_migration_list.MigrationListMailAddressPair]
        """
        self._mail_address_pairs = mail_address_pairs

    @property
    def mail_address_pairs(self):
        """List of mail address pairs (source and target mail addresses) to transfer.

        :rtype: list[parallels.plesk.source.mail.mail_migration_list.MigrationListMailAddressPair]
        """
        return self._mail_address_pairs


class MigrationListMailAddressPair(Entity):
    """Pair of mail addresses to perform transfer (source and target mail addresses)"""

    def __init__(
        self,
        source_email, source_password, encrypted_source_password,
        target_mailname, target_password, encrypted_target_password,
        imap_host, imap_port, ignore_security_warnings,
        imap_encryption, imap_timeout, imap_folder_separator
    ):
        self._source_email = source_email
        self._source_password = source_password
        self._encrypted_source_password = encrypted_source_password
        self._target_mailname = target_mailname
        self._target_password = target_password
        self._encrypted_target_password = encrypted_target_password
        self._imap_host = imap_host
        self._imap_port = imap_port
        self._ignore_security_warnings = ignore_security_warnings
        self._imap_encryption = imap_encryption
        self._imap_timeout = imap_timeout
        self._imap_folder_separator = imap_folder_separator

    @property
    def source_email(self):
        """
        :rtype: str | unicode
        """
        return self._source_email

    @property
    def source_password(self):
        """
        :rtype: str | unicode
        """
        return self._source_password

    @property
    def encrypted_source_password(self):
        """
        :rtype: str | unicode
        """
        return self._encrypted_source_password

    @property
    def target_mailname(self):
        """
        :rtype: str | unicode
        """
        return self._target_mailname

    @property
    def target_password(self):
        """
        :rtype: str | unicode | None
        """
        return self._target_password

    @property
    def encrypted_target_password(self):
        """
        :rtype: str | unicode
        """
        return self._encrypted_target_password

    @property
    def imap_host(self):
        """
        :rtype: str | unicode
        """
        return self._imap_host

    @property
    def imap_port(self):
        """
        :rtype: int | None
        """
        return self._imap_port

    @property
    def ignore_security_warnings(self):
        """
        :rtype: bool
        """
        return self._ignore_security_warnings

    @property
    def imap_encryption(self):
        """
        :rtype: str | None
        """
        return self._imap_encryption

    @property
    def imap_timeout(self):
        """
        :rtype: int | None
        """
        return self._imap_timeout

    @property
    def imap_folder_separator(self):
        """
        :rtype: str | None
        """
        return self._imap_folder_separator

    def as_encrypted_dictionary(self):
        return dict(
            source_email=self.source_email,
            source_password=self.encrypted_source_password,
            target_mailname=self.target_mailname,
            target_password=self.encrypted_target_password,
            imap_host=self.imap_host,
            imap_port=self.imap_port,
            ignore_security_warnings=self.ignore_security_warnings,
            imap_encryption=self.imap_encryption,
            imap_timeout=self.imap_timeout,
            imap_folder_separator=self.imap_folder_separator
        )


class MailMigrationListReader(object):
    def read(self, filename):
        """Read site migration list from file

        :type filename: str | unicode
        :rtype: parallels.plesk.source.mail.mail_migration_list.MailMigrationList
        """
        data_unencrypted = load_mail_migration_json_unencrypted_from_file(filename)
        data_encrypted = load_mail_migration_json_encrypted_from_file(filename)

        return MailMigrationList(
            mail_address_pairs=self._read_mail_address_pairs(data_unencrypted, data_encrypted)
            if data_unencrypted is not None else []
        )

    def _read_mail_address_pairs(self, migration_list_data_unencrypted, migration_list_data_encrypted):
        """
        :type migration_list_data_unencrypted: dict
        :rtype: list[parallels.plesk.source.mail.mail_migration_list.MigrationListMailAddressPair]
        """
        mail_address_pairs = []

        if 'mail_address_pairs' not in migration_list_data_unencrypted:
            return []

        encrypted_data = {
            (address_pair.get('source_email'), address_pair.get('target_mailname')): address_pair
            for address_pair in migration_list_data_encrypted.get('mail_address_pairs', {})
        }

        for mail_address_pair_data in safe_get_list(migration_list_data_unencrypted, 'mail_address_pairs'):
            mail_address_pair_data = safe_dict(mail_address_pair_data)
            encrypted_data_item = encrypted_data.get(
                (mail_address_pair_data.get('source_email'), mail_address_pair_data.get('target_mailname')), dict()
            )

            target_mailname = mail_address_pair_data.get('target_mailname')
            if not self._is_valid_mailname(target_mailname):
                raise MigrationNoContextError(safe_format(messages.INVALID_TARGET_MAILNAME, mailname=target_mailname))

            mail_address_pairs.append(
                MigrationListMailAddressPair(
                    source_email=mail_address_pair_data.get('source_email'),
                    source_password=mail_address_pair_data.get('source_password'),
                    encrypted_source_password=encrypted_data_item.get('source_password'),
                    target_mailname=target_mailname,
                    target_password=mail_address_pair_data.get('target_password'),
                    encrypted_target_password=encrypted_data_item.get('target_password'),
                    imap_host=mail_address_pair_data.get('imap_host'),
                    imap_port=mail_address_pair_data.get('imap_port'),
                    ignore_security_warnings=mail_address_pair_data.get('ignore_security_warnings'),
                    imap_encryption=mail_address_pair_data.get('imap_encryption'),
                    imap_timeout=mail_address_pair_data.get('imap_timeout'),
                    imap_folder_separator=mail_address_pair_data.get('imap_folder_separator'),
                )
            )

        return mail_address_pairs

    @staticmethod
    def _is_valid_mailname(mailname):
        return re.match("^[\w\-'][\w&+\-']*(\.[\w&+\-']+)*$", mailname) is not None
