from parallels.core.actions.base.subscription_action import SubscriptionAction
from parallels.core.utils.common import safe_string_repr
from parallels.plesk.utils.xml_rpc.plesk.core import PleskError
from parallels.plesk.utils.xml_rpc.plesk.operator import AliasOperator, DnsOperator
from parallels.plesk.source.plesk import messages


class AddSourceToTargetDNSAXFR(SubscriptionAction):
    def get_description(self):
        return messages.ACTION_ADD_SOURCE_SERVER_TO_TARGET_DNS_AXFR_DESCRIPTION

    def get_failure_message(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        return messages.ACTION_ADD_SOURCE_SERVER_TO_TARGET_DNS_AXFR_FAILURE

    def filter_subscription(self, global_context, subscription):
        """Check whether we should run this action or not. By default True - action should be executed.

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: bool
        """
        # skip this action for subscriptions w/o web source or for unix subscriptions
        if subscription.web_source_server is None or not subscription.web_source_server.is_windows():
            return False
        return True

    def run(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        cli_runner = global_context.conn.target.plesk_cli_runner
        plesk_api = subscription.panel_target_server.plesk_api()

        subscription_dns_ips = set(subscription.source_dns_ips)
        # additionally to source DNS IPs, add all IPs of the source panel server
        # because zone transfer requests may come from these IPs, not IP the DNS server is listening on
        subscription_dns_ips.update([
            ip.ip_address for ip in subscription.web_source_server.get_all_ips(global_context)
        ])

        for dns_ip in subscription_dns_ips:
            for domain in subscription.converted_dump.iter_domains():
                if domain.dns_zone is not None and domain.dns_zone.enabled:
                    try:
                        cli_runner.run('dns', ['--add', domain.name.encode('idna'), '-axfr', dns_ip])
                    except Exception as e:
                        if 'DNS record already exists' in safe_string_repr(e):
                            # if DNS record already exists (for example, was added
                            # during previous set-dns-forwarding command execution) then
                            # there no need to add it again, just ignore the exception with such message
                            pass
                        else:
                            # all the other exceptions should be handled
                            raise

        # we have to handle domain aliases in a special way with 2 API requests (get alias ID, then add record)
        # because Plesk 12 does not allow to create AXFR records for aliases with single CLI command
        for alias in subscription.converted_dump.iter_aliases():
            alias_id = plesk_api.send(
                AliasOperator.Get(
                    filter=AliasOperator.FilterByName([alias.name]),
                )
            )[0].data[0]

            for dns_ip in subscription_dns_ips:
                try:
                    plesk_api.send(
                        DnsOperator.AddRec(
                            target=DnsOperator.AddRec.TargetSiteAliasId(id=alias_id),
                            src='',
                            dst=dns_ip,
                            opt=None,
                            rec_type='AXFR'
                        )
                    )[0].check()
                except PleskError as e:
                    if e.code == 1007:
                        # Ignore situation when IP address already exists.
                        pass
                    else:
                        raise
