import itertools
from xml.etree import ElementTree

from parallels.core.logging import get_logger
from parallels.core.runners.base import BaseRunner
from parallels.core.utils.common import open_no_inherit, safe_string_repr
from parallels.core.utils.migrator_utils import safe_idn_decode
from parallels.core.utils.pmm.agent import DumpAll
from parallels.plesk.source.legacy.pmm_agent import UnixPmmMigrationAgent
from parallels.plesk.source.ppcpl import messages

logger = get_logger(__name__)


class PPCPLPmmMigrationAgent(UnixPmmMigrationAgent):
    def __init__(self, global_context, server, migrator_pmm_dir, settings):
        super(PPCPLPmmMigrationAgent, self).__init__(global_context, server, migrator_pmm_dir, settings)

    def _run(self, local_data_filename, local_log_filename, selection=DumpAll()):
        super(PPCPLPmmMigrationAgent, self)._run(local_data_filename, local_log_filename, selection)
        try:
            self._fix_idn_names_from_punycode(local_data_filename)
        except Exception as e:
            logger.exception()
            logger.fwarn(messages.FAILED_TO_FIX_IDN_NAMES, reason=e)

    @staticmethod
    def _fix_idn_names_from_punycode(local_data_filename):
        """Fix IDN names from punycode to human-readable unicode

        We do that here, not on agent side, because:
        1) PPCPL does not store unicode name in database or on file system, but stores them in punycode.
        But in GUI it shows domains in unicode.
        2) We don't want to add additional dependencies for Perl code for punycode to unicode conversions.

        :type local_data_filename: str | unicode
        """
        tree = ElementTree.parse(local_data_filename)
        for client_node in itertools.chain(
            tree.findall('admin/resellers/reseller/clients/client'),
            tree.findall('admin/clients/client')
        ):
            # Add unicode contact name for each customer
            client_node.attrib['contact'] = safe_idn_decode(client_node.attrib['name'])

            # Fix domain and site names, domain aliases, e-mail forwardings
            for domain_node in client_node.findall('domains/domain'):
                domain_node.attrib['name'] = safe_idn_decode(domain_node.attrib['name'])
                for site_node in domain_node.findall('phosting/sites/site'):
                    site_node.attrib['name'] = safe_idn_decode(site_node.attrib['name'])
                    site_node.attrib['parent-domain-name'] = safe_idn_decode(site_node.attrib['parent-domain-name'])

                for domain_alias_node in domain_node.findall('preferences/domain-alias'):
                    domain_alias_node.attrib['name'] = safe_idn_decode(domain_alias_node.attrib['name'])

                for mail_forwarding_node in domain_node.findall(
                    'mailsystem/mailusers/mailuser/preferences/forwarding'
                ):
                    if '@' in mail_forwarding_node.text:
                        user, domain = mail_forwarding_node.text.split('@', 1)
                        mail_forwarding_node.text = '@'.join([user, safe_idn_decode(domain)])

        with open_no_inherit(local_data_filename, 'w') as fp:
            fp.write(ElementTree.tostring(tree.getroot(), 'utf-8', 'xml'))

    def get_domain_subdomain_dirs(self, domain_name):
        """Run script that gets full paths to subdomain directories.

        Returns dictionary: {subdomain_short_name: (www_root, cgi_root)}
        """
        with self._source_server.runner() as runner:
            assert isinstance(runner, BaseRunner)
            output = runner.execute_command(
                u'cd {path}; {perl} ListSubdomainDirectories.pl {domain_name}',
                dict(
                    path=self.agent_dir,
                    perl=self._perl_bin,
                    domain_name=domain_name.encode('idna')
                )
            ).stdout.strip()

        result = {}

        lines = output.split("\n")
        for line in lines:
            if line.strip() != '':
                subdomain, www_root, cgi_root = [item.strip() for item in line.split("\t")]
                result[safe_string_repr(subdomain, omit_quotes=True)] = (
                    safe_string_repr(www_root, omit_quotes=True),
                    safe_string_repr(cgi_root, omit_quotes=True)
                )

        return result

    def get_mysql_admin_password(self):
        """Run script that gets MySQL admin password to copy database content
        
        Return string - the password
        
        :rtype: unicode
        """

        with self._source_server.runner() as runner:
            assert isinstance(runner, BaseRunner)
            return safe_string_repr(
                runner.execute_command(
                    u'cd {path}; {perl} MySQLAdminPassword.pl',
                    dict(
                        path=self.agent_dir,
                        perl=self._perl_bin,
                    )
                ).stdout,
                omit_quotes=True
            )

    def convert_mail(self, domain_root, user_name, converted_mail_dir):
        with self._source_server.runner() as runner:
            assert isinstance(runner, BaseRunner)
            runner.execute_command(
                u'cd {path}; {perl} ConvertMail.pl {domain_root} {user_name} {converted_mail_dir}',
                dict(
                    path=self.agent_dir,
                    perl=self._perl_bin,
                    domain_root=domain_root,
                    user_name=user_name,
                    converted_mail_dir=converted_mail_dir
                )
            )

    def _is_dump_agent_creates_archive(self):
        """Whether dump agent creates ready to use dump archive (ZIP or TAR). If False then agent creates plain XML.

        :rtype: bool
        """
        return False

    def _is_dump_version_upgrade_required(self):
        """Whether dump agent creates dump in old format (< 17.0), and upgrade is required to use with target Plesk 18

        :rtype: bool
        """
        return True
