import logging
from django.db.models.signals import post_delete, post_save, pre_save
from django.dispatch import receiver
from netaddr import IPNetwork

from ipam.models import IPAddress
from netbox_ddns.background_tasks import dns_create, dns_delete
from netbox_ddns.models import DNSStatus, ExtraDNSName
from netbox_ddns.utils import normalize_fqdn

logger = logging.getLogger('netbox_ddns')


@receiver(pre_save, sender=IPAddress)
def store_original_ipaddress(instance: IPAddress, **_kwargs):
    instance.before_save = IPAddress.objects.filter(pk=instance.pk).first()


@receiver(post_save, sender=IPAddress)
def trigger_ddns_update(instance: IPAddress, **_kwargs):
    old_address = instance.before_save.address.ip if instance.before_save else None
    old_dns_name = normalize_fqdn(instance.before_save.dns_name) if instance.before_save else ''

    new_address = IPNetwork(instance.address).ip
    new_dns_name = normalize_fqdn(instance.dns_name)

    extra_dns_names = {normalize_fqdn(extra.name): extra for extra in instance.extradnsname_set.all()}

    if new_address != old_address or new_dns_name != old_dns_name:
        status, created = DNSStatus.objects.get_or_create(ip_address=instance)

        if old_address and old_dns_name and old_dns_name not in extra_dns_names:
            delete = dns_delete.delay(
                dns_name=old_dns_name,
                address=old_address,
                status=status,
            )
        else:
            delete = None

        if new_address and new_dns_name:
            dns_create.delay(
                dns_name=new_dns_name,
                address=new_address,
                status=status,
                depends_on=delete,
            )

    if old_address != new_address:
        # This affects extra names
        for dns_name, status in extra_dns_names.items():
            # Don't touch the main dns_name
            if dns_name == old_dns_name or dns_name == new_dns_name:
                continue

            if old_dns_name:
                delete = dns_delete.delay(
                    dns_name=dns_name,
                    address=old_address,
                    reverse=False,
                    status=status,
                )
            else:
                delete = None

            if new_dns_name:
                dns_create.delay(
                    dns_name=dns_name,
                    address=new_address,
                    reverse=False,
                    status=status,
                    depends_on=delete,
                )


@receiver(post_delete, sender=IPAddress)
def trigger_ddns_delete(instance: IPAddress, **_kwargs):
    old_address = instance.address.ip
    old_dns_name = normalize_fqdn(instance.dns_name)

    if old_address and old_dns_name:
        dns_delete.delay(
            address=old_address,
            dns_name=old_dns_name,
        )


@receiver(pre_save, sender=ExtraDNSName)
def store_original_extra(instance: ExtraDNSName, **_kwargs):
    instance.before_save = ExtraDNSName.objects.filter(pk=instance.pk).first()


@receiver(post_save, sender=ExtraDNSName)
def trigger_extra_ddns_update(instance: ExtraDNSName, **_kwargs):
    address = instance.ip_address.address.ip
    old_dns_name = instance.before_save.name if instance.before_save else ''
    new_dns_name = instance.name

    if new_dns_name != old_dns_name:
        if old_dns_name:
            delete = dns_delete.delay(
                dns_name=old_dns_name,
                address=address,
                reverse=False,
                status=instance,
            )
        else:
            delete = None

        dns_create.delay(
            dns_name=new_dns_name,
            address=address,
            status=instance,
            reverse=False,
            depends_on=delete,
        )


@receiver(post_delete, sender=ExtraDNSName)
def trigger_extra_ddns_delete(instance: ExtraDNSName, **_kwargs):
    address = instance.ip_address.address.ip
    old_dns_name = instance.name

    if old_dns_name == normalize_fqdn(instance.ip_address.dns_name):
        return

    dns_delete.delay(
        dns_name=old_dns_name,
        address=address,
        reverse=False,
    )
