import time
from gettext import gettext as _
from itertools import chain
from string import Template

from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models, transaction

from cyder.base.eav.constants import ATTRIBUTE_INVENTORY
from cyder.base.eav.fields import EAVAttributeField
from cyder.base.eav.models import Attribute, EAVBase
from cyder.base.mixins import ObjectUrlMixin, DisplayMixin
from cyder.base.models import BaseModel
from cyder.base.validators import validate_positive_integer_field
from cyder.base.utils import transaction_atomic
from cyder.cydns.validation import validate_fqdn, validate_ttl
from cyder.core.task.models import Task
from cyder.settings import MIGRATING


#TODO, put these defaults in a config file.
ONE_WEEK = 604800
DEFAULT_EXPIRE = ONE_WEEK * 2
DEFAULT_RETRY = ONE_WEEK / 7  # One day
DEFAULT_REFRESH = 180  # 3 min
DEFAULT_MINIMUM = 180  # 3 min


class SOA(BaseModel, ObjectUrlMixin, DisplayMixin):
    """
    SOA stands for Start of Authority

        "An SOA record is required in each *db.DOMAIN* and *db.ADDR* file."

        -- O'Reilly DNS and BIND

    The structure of an SOA::

        <name>  [<ttl>]  [<class>]  SOA  <origin>  <person>  (
                           <serial>
                           <refresh>
                           <retry>
                           <expire>
                           <minimum> )


        >>> SOA(primary=primary, contact=contact, retry=retry,
        ... refresh=refresh, description=description)

    Each DNS zone must have it's own SOA object. Use the description field to
    remind yourself which zone an SOA corresponds to if different SOA's have a
    similar ``primary`` and ``contact`` value.
    """

    pretty_type = 'SOA'

    id = models.AutoField(primary_key=True)
    ttl = models.PositiveIntegerField(default=3600, blank=True, null=True,
                                      validators=[validate_ttl],
                                      verbose_name="Time to live")
    primary = models.CharField(max_length=100, validators=[validate_fqdn])
    contact = models.CharField(max_length=100, validators=[validate_fqdn])
    serial = models.PositiveIntegerField(
        null=False, default=int(time.time()),
        validators=[validate_positive_integer_field])
    # Indicates when the zone data is no longer authoritative. Used by slave.
    expire = models.PositiveIntegerField(
        null=False, default=DEFAULT_EXPIRE,
        validators=[validate_positive_integer_field])
    # The time between retries if a slave fails to contact the master
    # when refresh (below) has expired.
    retry = models.PositiveIntegerField(
        null=False, default=DEFAULT_RETRY,
        validators=[validate_positive_integer_field])
    # The time when the slave will try to refresh the zone from the master
    refresh = models.PositiveIntegerField(
        null=False, default=DEFAULT_REFRESH,
        validators=[validate_positive_integer_field])
    minimum = models.PositiveIntegerField(
        null=False, default=DEFAULT_MINIMUM,
        validators=[validate_positive_integer_field])
    description = models.CharField(max_length=200, blank=True)
    root_domain = models.ForeignKey("cyder.Domain", null=False, unique=True,
                                    related_name="root_of_soa")
    # This indicates if this SOA's zone needs to be rebuilt
    dirty = models.BooleanField(default=False)
    is_signed = models.BooleanField(default=False)
    dns_enabled = models.BooleanField(default=True)

    search_fields = ('primary', 'contact', 'description', 'root_domain__name')
    sort_fields = ('root_domain__name',)
    template = _("{root_domain}. {ttl} {rdclass:$rdclass_just} "
                 "{rdtype:$rdtype_just}" "{primary}. {contact}. ({serial} "
                 "{refresh} {retry} {expire})")

    attrs = None

    class Meta:
        app_label = 'cyder'
        db_table = 'soa'
        # We are using the description field here to stop the same SOA from
        # being assigned to multiple zones. See the documentation in the
        # Domain models.py file for more info.

    def bind_render_record(self):
        template = Template(self.template).substitute(**self.justs)
        return template.format(root_domain=self.root_domain,
                               rdtype=self.rdtype, rdclass='IN',
                               **self.__dict__)

    def __unicode__(self):
        return self.root_domain.name

    @staticmethod
    def filter_by_ctnr(ctnr, objects=None):
        objects = objects or SOA.objects
        domains = ctnr.domains.values_list('soa')
        return objects.filter(root_domain__id__in=domains)

    def check_in_ctnr(self, ctnr):
        return self.root_domain in ctnr.domains.all()

    @property
    def rdtype(self):
        return 'SOA'

    def details(self):
        """For tables."""
        data = super(SOA, self).details()
        data['data'] = [
            ('Root Domain', 'root_domain__name', self.root_domain),
            ('Primary', 'primary', self.primary),
            ('Contact', 'contact', self.contact),
            ('Serial', 'serial', self.serial),
            ('Enabled', 'dns_enabled', self.dns_enabled),
        ]
        return data

    @staticmethod
    def eg_metadata():
        """EditableGrid metadata."""
        return {'metadata': [
            {'name': 'description', 'datatype': 'string', 'editable': True},
            {'name': 'primary', 'datatype': 'string', 'editable': True},
            {'name': 'contact', 'datatype': 'string', 'editable': True},
            {'name': 'serial', 'datatype': 'integer', 'editable': True},
            {'name': 'expire', 'datatype': 'integer', 'editable': True},
            {'name': 'retry', 'datatype': 'integer', 'editable': True},
            {'name': 'refresh', 'datatype': 'integer', 'editable': True},
        ]}

    def get_debug_build_url(self):
        return reverse('build-debug', args=[self.pk])

    @transaction_atomic
    def delete(self, *args, **kwargs):
        root_domain = self.root_domain
        super(SOA, self).delete(*args, **kwargs)
        root_domain.soa = None
        # If we don't set the SOA to None, Django complains that the SOA
        # doesn't exist (which is true).
        root_domain.save(commit=False)
        reassign_reverse_records(root_domain, None)

    def has_record_set(self, view=None, exclude_ns=False):
        for domain in self.domain_set.all():
            if domain.has_record_set(view=view, exclude_ns=exclude_ns):
                return True
        return False

    def schedule_rebuild(self, save=True, force=False):
        if MIGRATING and not force:
            return

        Task.schedule_zone_rebuild(self)
        self.dirty = True
        if save:
            self.save()

    @transaction_atomic
    def save(self, *args, **kwargs):
        self.full_clean()

        is_new = self.pk is None

        if is_new:
            self.dirty = True