from django.core.management import BaseCommand, CommandError, call_command
from django.db import transaction

from desecapi import pdns
from desecapi.exceptions import PDNSException
from desecapi.models import Domain
from desecapi.pdns_change_tracker import PDNSChangeTracker


class Command(BaseCommand):
    help = 'Sync RRsets from local API database to pdns.'

    def add_arguments(self, parser):
        parser.add_argument('domain-name', nargs='*',
                            help='Domain name to sync. If omitted, will import all API domains.')

    def handle(self, *args, **options):
        domains = Domain.objects.all()

        if options['domain-name']:
            domains = domains.filter(name__in=options['domain-name'])
            domain_names = domains.values_list('name', flat=True)

            for domain_name in options['domain-name']:
                if domain_name not in domain_names:
                    raise CommandError('{} is not a known domain'.format(domain_name))

        catalog_alignment = False
        for domain in domains:
            self.stdout.write('%s ...' % domain.name, ending='')
            try:
                created = self._sync_domain(domain)
                if created:
                    self.stdout.write(f' created (was missing) ...', ending='')
                    catalog_alignment = True
                self.stdout.write(' synced')
            except Exception as e:
                self.stdout.write(' failed')
                msg = 'Error while processing {}: {}'.format(domain.name, e)
                raise CommandError(msg)

        if catalog_alignment:
            call_command('align-catalog-zone')

    @staticmethod
    @transaction.atomic
    def _sync_domain(domain):
        created = False

        # Create domain on pdns if it does not exist
        try:
            PDNSChangeTracker.CreateDomain(domain_name=domain.name).pdns_do()
        except PDNSException as e:
            # Domain already exists
            if e.response.status_code == 409:
                pass
            else:
                raise e
        else:
            created = True

        # modifications actually merged with additions in CreateUpdateDeleteRRSets
        modifications = {(rrset.type, rrset.subname) for rrset in domain.rrset_set.all()}
        deletions = {(rrset['type'], rrset['subname']) for rrset in pdns.get_rrset_datas(domain)} - modifications
        deletions.discard(('SOA', ''))  # do not remove SOA record

        # Update zone on nslord, propagate to nsmaster
        PDNSChangeTracker.CreateUpdateDeleteRRSets(domain.name, set(), modifications, deletions).pdns_do()
        pdns._pdns_put(pdns.NSMASTER, '/zones/{}/axfr-retrieve'.format(pdns.pdns_id(domain.name)))

        return created
