"""
This module contains models related to domain names.

"""
from __future__ import absolute_import, unicode_literals

from django.db import models, transaction
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _

from model_utils.models import TimeStampedModel
from model_utils import Choices


DNS_DOMAIN_TYPES = Choices(
    ('MASTER', _('Master')),
    ('SLAVE', _('Slave')),
    ('NATIVE', _('Native')),
)

# see https://doc.powerdns.com/md/authoritative/domainmetadata/
DNS_DOMAIN_METADATA_KINDS = Choices(
    'ALLOW-DNSUPDATE-FROM',
    'ALSO-NOTIFY',
    'AXFR-MASTER-TSIG',
    'AXFR-SOURCE',
    'FORWARD-DNSUPDATE',
    'GSS-ACCEPTOR-PRINCIPAL',
    'GSS-ALLOW-AXFR-PRINCIPAL',
    'LUA-AXFR-SCRIPT',
    'NSEC3NARROW',
    'NSEC3PARAM',
    'PRESIGNED',
    'PUBLISH_CDNSKEY',
    'PUBLISH_CDS',
    'SOA-EDIT',
    'SOA-EDIT-DNSUPDATE',
    'TSIG-ALLOW-AXFR',
    'TSIG-ALLOW-DNSUPDATE',
)

DNS_TSIG_KEY_ALGORITHMS = Choices(
    ('hmac-md5', _('HMAC MD5')),
    ('hmac-sha1', _('HMAC SHA1')),
    ('hmac-sha224', _('HMAC SHA224')),
    ('hmac-sha256', _('HMAC SHA256')),
    ('hmac-sha384', _('HMAC SHA384')),
    ('hmac-sha512', _('HMAC SHA512')),
)


@python_2_unicode_compatible
class DomainBase(TimeStampedModel):
    """
    This is the base model for domains.

    """
    domain = models.CharField(_('domain name'), max_length=255, unique=True)
    customer = models.ForeignKey(
        settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True,
        null=True)

    class Meta:
        abstract = True

    def __str__(self):
        return self.domain


@python_2_unicode_compatible
class MailDomain(DomainBase):
    """
    This is the model for mail domains. Mail domains are used to configure the
    mail servers (SMTP/IMAP/POP3). Mail addresses are assigned to these mail
    domains.

    """
    class Meta:
        verbose_name = _('Mail domain')
        verbose_name_plural = _('Mail domains')

    def __str__(self):
        return self.domain

    def get_mailaddresses(self):
        """
        Get a list of mail addresses assigned to this mail domain.

        """
        return self.mailaddress_set.all()
    mailaddresses = property(get_mailaddresses)


class HostingDomainManager(models.Manager):
    """
    Default Manager for :py:class:`HostingDomain`.

    """
    @transaction.atomic
    def create_for_hosting_package(
        self, hosting_package, domain, commit, **kwargs
    ):
        from hostingpackages.models import CustomerHostingPackageDomain
        hostingdomain = self.create(
            customer=hosting_package.customer, domain=domain, **kwargs)
        hostingdomain.maildomain = MailDomain.objects.create(
            customer=hosting_package.customer, domain=domain)
        custdomain = CustomerHostingPackageDomain.objects.create(
            hosting_package=hosting_package, domain=hostingdomain)
        if commit:
            hostingdomain.save()
            custdomain.save()
        return hostingdomain


@python_2_unicode_compatible
class HostingDomain(DomainBase):
    """
    This is the model for hosting domains. A hosting domain is linked to a
    customer hosting account.

    """
    maildomain = models.OneToOneField(
        MailDomain, verbose_name=_('mail domain'), blank=True, null=True,
        help_text=_('assigned mail domain for this domain')
    )

    objects = HostingDomainManager()

    class Meta:
        verbose_name = _('Hosting domain')
        verbose_name_plural = _('Hosting domains')

    def __str__(self):
        return self.domain


@python_2_unicode_compatible
class DNSDomain(DomainBase):
    """
    This model represents a DNS zone. The model is similar to the domain table
    in the PowerDNS schema specified in
    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.

    .. code-block:: sql

       CREATE TABLE domains (
         id                    SERIAL PRIMARY KEY,
         name                  VARCHAR(255) NOT NULL,
         master                VARCHAR(128) DEFAULT NULL,
         last_check            INT DEFAULT NULL,
         type                  VARCHAR(6) NOT NULL,
         notified_serial       INT DEFAULT NULL,
         account               VARCHAR(40) DEFAULT NULL,
         CONSTRAINT c_lowercase_name CHECK (
             ((name)::TEXT = LOWER((name)::TEXT)))
       );

       CREATE UNIQUE INDEX name_index ON domains(name);

    """
    # name is represented by domain
    master = models.CharField(max_length=128, blank=True, null=True)
    last_check = models.IntegerField(null=True)
    domaintype = models.CharField(
        max_length=6, choices=DNS_DOMAIN_TYPES, db_column='type')
    notified_serial = models.IntegerField(null=True)
    # account is represented by customer_id
    # check constraint is added via RunSQL in migration

    class Meta:
        verbose_name = _('DNS domain')
        verbose_name_plural = _('DNS domains')

    def __str__(self):
        return self.domain


@python_2_unicode_compatible
class DNSRecord(models.Model):
    """
    This model represents a DNS record. The model is similar to the record
    table in the PowerDNS schema specified in
    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.

    .. code-block:: sql

       CREATE TABLE records (
         id                    SERIAL PRIMARY KEY,
         domain_id             INT DEFAULT NULL,
         name                  VARCHAR(255) DEFAULT NULL,
         type                  VARCHAR(10) DEFAULT NULL,
         content               VARCHAR(65535) DEFAULT NULL,
         ttl                   INT DEFAULT NULL,
         prio                  INT DEFAULT NULL,
         change_date           INT DEFAULT NULL,
         disabled              BOOL DEFAULT 'f',
         ordername             VARCHAR(255),
         auth                  BOOL DEFAULT 't',
         CONSTRAINT domain_exists
         FOREIGN KEY(domain_id) REFERENCES domains(id)
         ON DELETE CASCADE,
         CONSTRAINT c_lowercase_name CHECK (
             ((name)::TEXT = LOWER((name)::TEXT)))
       );

       CREATE INDEX rec_name_index ON records(name);
       CREATE INDEX nametype_index ON records(name,type);
       CREATE INDEX domain_id ON records(domain_id);
       CREATE INDEX recordorder ON records (
           domain_id, ordername text_pattern_ops);

    """
    domain = models.ForeignKey('DNSDomain')
    name = models.CharField(
        max_length=255, blank=True, null=True, db_index=True)
    recordtype = models.CharField(
        max_length=10, blank=True, null=True, db_column='type')
    content = models.CharField(max_length=65535, blank=True, null=True)
    ttl = models.IntegerField(null=True)
    prio = models.IntegerField(null=True)
    change_date = models.IntegerField(null=True)
    disabled = models.BooleanField(default=False)
    ordername = models.CharField(max_length=255)
    auth = models.BooleanField(default=True)
    # check constraint and index recordorder are added via RunSQL in migration

    class Meta:
        verbose_name = _('DNS record')
        verbose_name_plural = _('DNS records')
        index_together = [
            ['name', 'recordtype']
        ]

    def __str__(self):
        return "{name} IN {type} {content}".format(
            name=self.name, type=self.recordtype, content=self.content)


@python_2_unicode_compatible
class DNSSupermaster(models.Model):
    """
    This model represents the supermasters table in the PowerDNS schema
    specified in
    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.

    .. code-block:: sql

       CREATE TABLE supermasters (
         ip                    INET NOT NULL,
         nameserver            VARCHAR(255) NOT NULL,
         account               VARCHAR(40) NOT NULL,
         PRIMARY KEY(ip, nameserver)
       );

    """
    ip = models.GenericIPAddressField()
    nameserver = models.CharField(max_length=255)
    # account is replaced by customer
    customer = models.ForeignKey(
        settings.AUTH_USER_MODEL, verbose_name=_('customer'))

    class Meta:
        verbose_name = _('DNS supermaster')
        verbose_name_plural = _('DNS supermasters')
        unique_together = (
            ('ip', 'nameserver')
        )

    def __str__(self):
        return "{ip} {nameserver}".format(
            ip=self.ip, nameserver=self.nameserver)


@python_2_unicode_compatible
class DNSComment(models.Model):
    """
    This model represents the comments table in the PowerDNS schema specified
    in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The
    comments table is used to store user comments related to individual DNS
    records.

    .. code-block:: sql

       CREATE TABLE comments (
         id                    SERIAL PRIMARY KEY,
         domain_id             INT NOT NULL,
         name                  VARCHAR(255) NOT NULL,
         type                  VARCHAR(10) NOT NULL,
         modified_at           INT NOT NULL,
         account               VARCHAR(40) DEFAULT NULL,
         comment               VARCHAR(65535) NOT NULL,
         CONSTRAINT domain_exists
         FOREIGN KEY(domain_id) REFERENCES domains(id)
         ON DELETE CASCADE,
         CONSTRAINT c_lowercase_name CHECK (
             ((name)::TEXT = LOWER((name)::TEXT)))
       );

       CREATE INDEX comments_domain_id_idx ON comments (domain_id);
       CREATE INDEX comments_name_type_idx ON comments (name, type);
       CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);

    """
    domain = models.ForeignKey('DNSDomain')
    name = models.CharField(max_length=255)
    commenttype = models.CharField(max_length=10, db_column='type')
    modified_at = models.IntegerField()
    # account is replaced by customer
    customer = models.ForeignKey(
        settings.AUTH_USER_MODEL, verbose_name=_('customer'))
    comment = models.CharField(max_length=65535)
    # check constraint is added via RunSQL in migration

    class Meta:
        verbose_name = _('DNS comment')
        verbose_name_plural = _('DNS comments')
        index_together = [
            ['name', 'commenttype'],
            ['domain', 'modified_at']
        ]

    def __str__(self):
        return "{name} IN {type}: {comment}".format(
            name=self.name, type=self.commenttype, comment=self.comment)


@python_2_unicode_compatible
class DNSDomainMetadata(models.Model):
    """
    This model represents the domainmetadata table in the PowerDNS schema
    specified in
    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.
    The domainmetadata table is used to store domain meta data as described in
    https://doc.powerdns.com/md/authoritative/domainmetadata/.

    .. code-block:: sql

       CREATE TABLE domainmetadata (
         id                    SERIAL PRIMARY KEY,
         domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
         kind                  VARCHAR(32),
         content               TEXT
       );

       CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);

    """
    domain = models.ForeignKey('DNSDomain')
    kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS)
    content = models.TextField()

    class Meta:
        verbose_name = _('DNS domain metadata item')
        verbose_name_plural = _('DNS domain metadata items')

    def __str__(self):
        return "{domain} {kind} {content}".format(
            domain=self.domain.domain, kind=self.kind, content=self.content)


@python_2_unicode_compatible
class DNSCryptoKey(models.Model):
    """
    This model represents the cryptokeys table in the PowerDNS schema
    specified in
    https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.

    .. code-block:: sql

       CREATE TABLE cryptokeys (
         id                    SERIAL PRIMARY KEY,
         domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
         flags                 INT NOT NULL,
         active                BOOL,
         content               TEXT
       );

       CREATE INDEX domainidindex ON cryptokeys(domain_id);

    """
    domain = models.ForeignKey('DNSDomain')
    flags = models.IntegerField()
    active = models.BooleanField(default=True)
    content = models.TextField()

    class Meta:
        verbose_name = _('DNS crypto key')
        verbose_name_plural = _('DNS crypto keys')

    def __str__(self):
        return "{domain} {content}".format(
            domain=self.domain.domain, content=self.content)


@python_2_unicode_compatible
class DNSTSIGKey(models.Model):
    """
    This model represents the tsigkeys table in the PowerDNS schema specified
    in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/.

    .. code-block:: sql

       CREATE TABLE tsigkeys (
         id                    SERIAL PRIMARY KEY,
         name                  VARCHAR(255),
         algorithm             VARCHAR(50),
         secret                VARCHAR(255),
         CONSTRAINT c_lowercase_name CHECK (
             ((name)::TEXT = LOWER((name)::TEXT)))
       );

       CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);

    """
    name = models.CharField(max_length=255)
    algorithm = models.CharField(
        max_length=50, choices=DNS_TSIG_KEY_ALGORITHMS)
    secret = models.CharField(max_length=255)
    # check constraint is added via RunSQL in migration

    class Meta:
        verbose_name = _('DNS TSIG key')
        verbose_name_plural = _('DNS TSIG keys')
        unique_together = [
            ['name', 'algorithm']
        ]

    def __str__(self):
        return "{name} {algorithm} XXXX".format(
            name=self.name, algorithm=self.algorithm)