import ipaddress

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _


class Prefix(models.Model):

    name = models.SlugField(
        unique=True,
        verbose_name=_("name"),
    )
    prefix = models.GenericIPAddressField(
        protocol='IPv6',
        unique=True,
        verbose_name=_("prefix"),
    )
    length = models.PositiveSmallIntegerField(
        verbose_name=_("length"),
    )
    delegated_length = models.PositiveSmallIntegerField(
        verbose_name=_("length of delegated prefixes"),
    )

    def network(self):
        return ipaddress.IPv6Network(f'{self.prefix}/{self.length}')

    def clean(self):
        try:
            self.network()
        except:
            raise ValidationError(f"{self.prefix}/{self.length} is not a valid network.")
        if self.length >= self.delegated_length:
            raise ValidationError(f"length ({self.length}) must be lower than delegated_length ({self.delegated_length}).")
        if self.delegated_length > 128:
            raise ValidationError(f"delegated_length {self.delegated_length} must be lower than or equal to 128.")

    def __str__(self):
        return str(self.network())

    class Meta:
        verbose_name = _("Prefix")
        verbose_name_plural = _("Prefixes")


class DelegatedPrefix(models.Model):

    prefix = models.ForeignKey(
        Prefix,
        on_delete=models.PROTECT,
        related_name='delegated_prefixes',
        verbose_name=_("prefix"),
    )
    delegated_prefix = models.GenericIPAddressField(
        protocol='IPv6',
        unique=True,
        verbose_name=_("delegated prefix"),
    )
    gateway = models.GenericIPAddressField(
        protocol='IPv6',
        verbose_name=_("gateway"),
    )
    owner = models.ForeignKey(
        settings.PREFIX_DELEGATION_OWNER,
        on_delete=models.CASCADE,
        verbose_name=_("owner"),
    )

    def network(self):
        return ipaddress.IPv6Network(f'{self.delegated_prefix}/{self.prefix.delegated_length}')

    def clean(self):
        try:
            network = self.network()
        except:
            raise ValidationError(f"{self.delegated_prefix}/{self.prefix.delegated_length} is not a valid network.")
        prefix_network = self.prefix.network()
        if not network.subnet_of(prefix_network):
            raise ValidationError(f"{network} must be a subnet of {prefix_network}.")

    def __str__(self):
        return str(self.network())

    class Meta:
        verbose_name = "Delegated prefix"
        verbose_name_plural = "Delegated prefixes"


class Nameserver(models.Model):

    name = models.CharField(
        max_length=253,
        unique=True,
    )

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Name server"
        verbose_name_plural = "Name servers"


class DelegatedReverseDns(models.Model):

    delegated_prefix = models.OneToOneField(
        DelegatedPrefix,
        on_delete=models.CASCADE,
        related_name='delegated_reverse_dns'
    )
    nameservers = models.ManyToManyField(
        Nameserver
    )

    def owner(self):
        return self.delegated_prefix.owner

    def clean(self):
        if self.delegated_prefix.prefix.delegated_length % 4:
            raise ValidationError(f"delegated_length must be a multiple of 4 in order to delegate a reverse DNS.")

    def __str__(self):
        network = self.delegated_prefix.network()
        zone = network.network_address.exploded.replace(':', '')[network.prefixlen//4-1::-1]
        zone = '.'.join(list(zone)) + '.ip6.arpa'
        return zone

    class Meta:
        verbose_name = "Delegated reverse DNS"
        verbose_name_plural = "Delegated reverse DNS"


class DsRecord(models.Model):

    delegated_reverse_dns = models.ForeignKey(
        DelegatedReverseDns,
        on_delete=models.CASCADE,
        related_name='ds_records'
    )
    key_tag = models.PositiveIntegerField()
    algorithm = models.PositiveSmallIntegerField()
    digest_type = models.PositiveSmallIntegerField()
    digest = models.TextField()

    def __str__(self):
        return f"{self.ket_tag} {self.algorithm} {self.digest_type} {self.digest}"

    class Meta:
        verbose_name = "DS record"
        verbose_name_plural = "DS records"