"""
This module contains the hosting package models.

"""
from __future__ import absolute_import, unicode_literals

from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _, ungettext

from model_utils import Choices
from model_utils.models import TimeStampedModel

from domains.models import HostingDomain
from managemails.models import Mailbox
from osusers.models import (
    AdditionalGroup,
    Group,
    User as OsUser,
)
from userdbs.models import DB_TYPES


DISK_SPACE_UNITS = Choices(
    (0, 'M', _('MiB')),
    (1, 'G', _('GiB')),
    (2, 'T', _('TiB')),
)

DISK_SPACE_FACTORS = (
    (1, None, None),
    (1024, 1, None),
    (1024 * 1024, 1024, 1),
)


@python_2_unicode_compatible
class HostingPackageBase(TimeStampedModel):
    description = models.TextField(_('description'), blank=True)
    mailboxcount = models.PositiveIntegerField(_('mailbox count'))
    diskspace = models.PositiveIntegerField(
        _('disk space'), help_text=_('disk space for the hosting package'))
    diskspace_unit = models.PositiveSmallIntegerField(
        _('unit of disk space'), choices=DISK_SPACE_UNITS)

    class Meta:
        abstract = True

    def __str__(self):
        return self.name


class HostingPackageTemplate(HostingPackageBase):
    name = models.CharField(_('name'), max_length=128, unique=True)

    class Meta:
        verbose_name = _('Hosting package')
        verbose_name_plural = _('Hosting packages')


class HostingOption(TimeStampedModel):
    """
    This is the base class for several types of hosting options.

    """


@python_2_unicode_compatible
class DiskSpaceOptionBase(models.Model):
    diskspace = models.PositiveIntegerField(_('disk space'))
    diskspace_unit = models.PositiveSmallIntegerField(
        _('unit of disk space'), choices=DISK_SPACE_UNITS)

    class Meta:
        abstract = True
        ordering = ['diskspace_unit', 'diskspace']
        unique_together = ['diskspace', 'diskspace_unit']
        verbose_name = _('Disk space option')
        verbose_name_plural = _('Disk space options')

    def __str__(self):
        return _("Additional disk space {space} {unit}").format(
            space=self.diskspace, unit=self.get_diskspace_unit_display())


class DiskSpaceOption(DiskSpaceOptionBase, HostingOption):
    """
    This is a class for hosting options adding additional disk space to
    existing hosting packages.

    """


@python_2_unicode_compatible
class UserDatabaseOptionBase(models.Model):
    number = models.PositiveIntegerField(
        _('number of databases'), default=1)
    db_type = models.PositiveSmallIntegerField(
        _('database type'), choices=DB_TYPES)

    class Meta:
        abstract = True
        ordering = ['db_type', 'number']
        unique_together = ['number', 'db_type']
        verbose_name = _('Database option')
        verbose_name_plural = _('Database options')

    def __str__(self):
        return ungettext(
            '{type} database',
            '{count} {type} databases',
            self.number
        ).format(
            type=self.get_db_type_display(), count=self.number
        )


class UserDatabaseOption(UserDatabaseOptionBase, HostingOption):
    """
    This is a class for hosting options adding user databases to existing
    hosting packages.

    """


@python_2_unicode_compatible
class MailboxOptionBase(models.Model):
    """
    Base class for mailbox options.

    """
    number = models.PositiveIntegerField(
        _('number of mailboxes'), unique=True)

    class Meta:
        abstract = True
        ordering = ['number']
        verbose_name = _('Mailbox option')
        verbose_name_plural = _('Mailbox options')

    def __str__(self):
        return ungettext(
            '{count} additional mailbox',
            '{count} additional mailboxes',
            self.number
        ).format(
            count=self.number
        )


class MailboxOption(MailboxOptionBase, HostingOption):
    """
    This is a class for hosting options adding more mailboxes to existing
    hosting packages.

    """


class CustomerHostingPackageManager(models.Manager):
    """
    This is the default manager implementation for
    :py:class:`CustomerHostingPackage`.

    """

    def create_from_template(self, customer, template, name, **kwargs):
        """
        Use this method to create a new :py:class:`CustomerHostingPackage` from
        a :py:class:`HostingPackageTemplate`.

        The method copies data from the template to the new
        :py:class:`CustomerHostingPackage` instance.

        :param customer: a Django user representing a customer
        :param template: a :py:class:`HostingPackageTemplate`
        :param str name: the name of the hosting package there must only be
            one hosting package of this name for each customer
        :return: customer hosting package
        :rtype: :py:class:`CustomerHostingPackage`

        """
        package = CustomerHostingPackage(
            customer=customer, template=template, name=name)
        package.description = template.description
        package.copy_template_attributes()
        if 'commit' in kwargs and kwargs['commit'] is True:
            package.save(**kwargs)
        return package


@python_2_unicode_compatible
class CustomerHostingPackage(HostingPackageBase):
    """
    This class defines customer specific hosting packages.

    """
    customer = models.ForeignKey(
        settings.AUTH_USER_MODEL, verbose_name=_('customer'))
    template = models.ForeignKey(
        HostingPackageTemplate, verbose_name=_('hosting package template'),
        help_text=_(
            'The hosting package template that this hosting package is based'
            ' on'
        ))
    name = models.CharField(_('name'), max_length=128)
    osuser = models.OneToOneField(
        OsUser, verbose_name=_('Operating system user'),
        blank=True, null=True)

    objects = CustomerHostingPackageManager()

    class Meta:
        unique_together = ['customer', 'name']
        verbose_name = _('customer hosting package')
        verbose_name_plural = _('customer hosting packages')

    def __str__(self):
        return _("{name} for {customer}").format(
            name=self.name, customer=self.customer
        )

    def get_absolute_url(self):
        return reverse('hosting_package_details', kwargs={
            'user': self.customer.username,
            'pk': self.id,
        })

    def copy_template_attributes(self):
        """
        Copy the attributes of the hosting package's template to the package.

        """
        for attrname in ('diskspace', 'diskspace_unit', 'mailboxcount'):
            setattr(self, attrname, getattr(self.template, attrname))

    def get_disk_space(self, unit=None):
        """
        Get the total disk space reserved for this hosting package and all its
        additional disk space options.

        :return: disk space
        :rtype: int

        """
        diskspace = self.diskspace
        min_unit = self.diskspace_unit

        options = CustomerDiskSpaceOption.objects.filter(hosting_package=self)
        for option in options:
            if option.diskspace_unit == min_unit:
                diskspace += option.disk_space
            elif option.diskspace_unit > min_unit:
                diskspace += (
                    DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] *
                    option.diskspace)
            else:
                diskspace = (
                    DISK_SPACE_FACTORS[min_unit][option.diskspace_unit] *
                    diskspace) + option.diskspace
                min_unit = option.diskspace_unit
        if unit is None:
            return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024**2
        if unit > min_unit:
            return DISK_SPACE_FACTORS[unit][min_unit] * diskspace
        return DISK_SPACE_FACTORS[min_unit][unit] * diskspace

    def get_quota(self):
        soft = 1024 * self.get_disk_space(DISK_SPACE_UNITS.M)
        hard = soft * 105 / 100
        return (soft, hard)

    def get_used_mailboxes(self):
        """
        Get the number of used mailboxes for this hosting package.

        """
        if self.osuser:
            return Mailbox.objects.filter(osuser=self.osuser).count()
        return 0

    def get_mailboxes(self):
        """
        Get the number of mailboxes provided by this hosting package and all
        of its mailbox options.

        """
        result = CustomerMailboxOption.objects.filter(
            hosting_package=self
        ).aggregate(
            mailbox_sum=models.Sum('number')
        )
        return self.mailboxcount + (result['mailbox_sum'] or 0)

    def get_databases(self):
        """
        Get the number of user databases provided by user database hosting
        options for this hosting package.

        """
        return CustomerUserDatabaseOption.objects.values(
            'db_type'
        ).filter(hosting_package=self).annotate(
            number=models.Sum('number')
        ).all()

    @transaction.atomic
    def save(self, *args, **kwargs):
        """
        Save the hosting package to the database.

        If this is a new hosting package a new operating system user is
        created and assigned to the hosting package.

        :param args: positional arguments to be passed on to
            :py:meth:`django.db.Model.save`
        :param kwargs: keyword arguments to be passed on to
            :py:meth:`django.db.Model.save`
        :return: self
        :rtype: :py:class:`CustomerHostingPackage`

        """
        if self.pk is None:
            self.copy_template_attributes()
            self.osuser = OsUser.objects.create_user(self.customer)
            for group in settings.OSUSER_DEFAULT_GROUPS:
                AdditionalGroup.objects.create(
                    user=self.osuser, group=Group.objects.get(groupname=group)
                )
        return super(CustomerHostingPackage, self).save(*args, **kwargs)


@python_2_unicode_compatible
class CustomerHostingPackageDomain(TimeStampedModel):
    """
    This class defines the relationship from a hosting package to a hosting
    domain.

    """
    hosting_package = models.ForeignKey(
        CustomerHostingPackage, verbose_name=_('hosting package'),
        related_name='domains')
    domain = models.OneToOneField(
        HostingDomain, verbose_name=_('hosting domain'))

    def __str__(self):
        return self.domain.domain

    def is_usable_for_email(self):
        """
        Tells wether the related domain is usable for email addresses.

        """
        return self.domain.maildomain is not None


class CustomerHostingPackageOption(TimeStampedModel):
    """
    This class defines options for customer hosting packages.

    """
    hosting_package = models.ForeignKey(
        CustomerHostingPackage, verbose_name=_('hosting package'))

    class Meta:
        verbose_name = _('customer hosting option')
        verbose_name_plural = _('customer hosting options')


class CustomerDiskSpaceOption(DiskSpaceOptionBase,
                              CustomerHostingPackageOption):
    """
    This is a class for customer hosting package options adding additional disk
    space to existing customer hosting package.

    """
    template = models.ForeignKey(
        DiskSpaceOption,
        verbose_name=_('disk space option template'),
        help_text=_(
            'The disk space option template that this disk space option is'
            ' based on'
        ))


class CustomerUserDatabaseOption(UserDatabaseOptionBase,
                                 CustomerHostingPackageOption):
    """
    This is a class for customer hosting package options adding user databases
    to existing customer hosting packages.

    """
    template = models.ForeignKey(
        UserDatabaseOption,
        verbose_name=_('user database option template'),
        help_text=_(
            'The user database option template that this database option is'
            ' based on'
        ))


class CustomerMailboxOption(MailboxOptionBase,
                            CustomerHostingPackageOption):
    """
    This is a class for customer hosting package options adding additional
    mailboxes to existing customer hosting packages.

    """
    template = models.ForeignKey(
        MailboxOption,
        verbose_name=_('mailbox option template'),
        help_text=_(
            'The mailbox option template that this mailbox option is based on'
        ))