""" This module holds the models of the ``brainstormers`` app. """

from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.functional import cached_property
from django.utils.text import Truncator
from django.utils.translation import ugettext_lazy as _

from django_extensions.db.models import TimeStampedModel

from mtp.apps.broker_models_hooks.models import BrokerHookMixin
from mtp.apps.common.models import Orderable
from mtp.apps.common.utils import compute_file_field_hash, upload_to_generator
from mtp.apps.likes.models import Likeable
from mtp.apps.sync.models import Syncable
from mtp.apps.trainings.models import TrainingActivity

from .constants import DEFAULT_GROUP_ATTRIBUTES
from .managers import (
    BrainstormerActivityManager,
    BrainstormerActivityQuerySet,
    BrainstormerManager,
    BrainstormerPropositionGroupManager,
    BrainstormerPropositionGroupQuerySet,
    BrainstormerPropositionManager,
    BrainstormerPropositionQuerySet,
    BrainstormerQuerySet,
)


class BrainstormerActivity(Syncable):
    """ A Brainstormer Activity.

    Attributes
    ----------
    name : django ``CharField``
        The name of this brainstormer activity
    preview : django ``ImageField``
        A preview image for this activity.
    hash_preview : django ``CharField``
        The hash of the previously defined ``preview`` file.
    image : django ``ImageField``
        The image used as background during a brainstormer.
        based on this activity.
    hash_image : django ``CharField``
        The hash of the previously defined ``image`` file.
    trainings : django ``ManyToManyField``
        The trainings that include brainstormers based on this activity.

    Notes
    -----
    This model is ``Syncable``.
    """

    name = models.CharField(max_length=100)

    preview = models.ImageField(
        upload_to=upload_to_generator,
        verbose_name=_("PreviewTheme")
    )
    hash_preview = models.CharField(max_length=36)

    image = models.ImageField(
        upload_to=upload_to_generator,
        verbose_name=_("ImageTheme"),
        blank=True, null=True
    )
    hash_image = models.CharField(max_length=36, blank=True)

    trainings = models.ManyToManyField('trainings.Training', blank=True)

    objects = BrainstormerActivityManager.from_queryset(
        BrainstormerActivityQuerySet)()

    class Meta:
        verbose_name_plural = _('brainstormer activities')

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.hash_image = compute_file_field_hash(self.image)
        self.hash_preview = compute_file_field_hash(self.preview)
        super().save(*args, **kwargs)

    @cached_property
    def media_files(self):
        """Get a list of media file paths for this brainstormer activity

        Returns
        -------
        list(str)
            A list of media relative paths for this brainstormer activity

        """

        return [
            self.preview.name,
            self.image.name,
        ]

    def get_sync_files(self):
        return [{
            'name': self.image.name,
            'hash': self.hash_image,
        }, {
            'name': self.preview.name,
            'hash': self.hash_preview,
        }]


class BrainstormerTemplate(TrainingActivity):
    """ A Template is a Brainstormer model.

    Attributes
    ----------
    name : django ``CharField``
        The name of this brainstormer template.
        Inherited from ``TrainingActivity``.
    training : django ``ForeignKey``
        The training this brainstormer template belongs to.
        Inherited from ``TrainingActivity``.
    sort_order : django ``PositiveIntegerField``
        The sort order of this brainstormer template.
        Inherited from ``Orderable``.
    activity : django ``ForeignKey``
        The ``BrainstormerActivity`` linked to this brainstormer template.

    """

    activity = models.ForeignKey('BrainstormerActivity', null=True, blank=True,
                                 verbose_name=_('activity'))

    verbose_activity_name = _('Brainstormer')

    class Meta:
        ordering = ['sort_order']

    def __str__(self):
        return '%s (template)' % self.name

    @classmethod
    def get_create_url(cls, training):
        """ Get the url of to create a new brainstormer template.

        Returns
        -------
        string
            The wanted url.

        """
        return reverse(
            'trainings:brainstormer-template-create',
            kwargs={'training_uuid': training.pk}
        )

    @classmethod
    def get_import_url(cls, training):
        """ Get the url of to import a new brainstormer template.

        Returns
        -------
        string
            The wanted url.

        """
        return reverse(
            'brainstormers:brainstormer-template-import-xls',
            kwargs={'training_uuid': training.pk}
        )

    def save(self, *args, **kwargs):
        """Manually crop the "name" field

        For the parameters, see ``django.db.models.Model``
        """

        if len(self.name) > 100:
            self.name = Truncator(self.name).chars(100, truncate='(...)')

        super().save(*args, **kwargs)

    def get_export_url(self):
        """ Get the url of to export a brainstormer template.

        Returns
        -------
        string
            The wanted url.

        """
        return reverse(
            'brainstormers:brainstormer-template-export-xls',
            kwargs={'brainstormer_template_uuid': self.pk}
        )

    def get_absolute_url(self):
        """ Get the url of the current brainstormer template.

        It actually returns the detail url.

        Returns
        -------
        string
            The wanted url.

        """
        return self.get_detail_url()

    def get_detail_url(self):
        """ Get the url of the current brainstormer template.

        It actually returns the edit url, as there is no "read" view
        for a brainstormer template.

        Returns
        -------
        string
            The wanted url.

        """
        return reverse(
            'brainstormers:template-detail',
            kwargs={'template_uuid': self.pk}
        )

    def get_edit_url(self):
        """ Get the url to edit the current brainstormer template.

        Returns
        -------
        string
            The wanted url.

        """
        return reverse(
            'brainstormers:template-update',
            kwargs={'template_uuid': self.pk}
        )

    def get_qrcode_url(self):
        """Get the url to download the QRCode able to launch activity.

        We disable this option for brainstormer template.

        """
        return None


class Brainstormer(Orderable, Syncable):
    """ A Brainstormer allows to organize propositions on a work subject
    through groups and an activity.

    A Brainstormer is built by copying a BrainstormerTemplate.
    However, it is linked to a training session instead of a training.

    Attributes
    ----------
    name : django ``CharField``
        The name of this brainstormer
    session : django ``ForeignKrey``
        The training session nthis brainstormer is a part of.
    activity : django ``ForeignKey``
        The brainstormer activity this brainstormer is built upon.

    Notes
    -----
    This model is ``Syncable`` and ``Orderable``.
    """

    name = models.CharField(_('name'), max_length=100)
    session = models.ForeignKey('trainings.TrainingSession')
    activity = models.ForeignKey('BrainstormerActivity',
                                 verbose_name=_('activity'), null=True, blank=True)
    anonymous = models.BooleanField(default=False)
    objects = BrainstormerManager.from_queryset(BrainstormerQuerySet)()

    class Meta:
        ordering = ['sort_order']

    def __str__(self):
        return '%s' % self.name

    def save(self, *args, **kwargs):
        """
        A Brainstormer can inherit of BrainstormerTemplate data, so before save it we verify
        if a template is given in kwargs for populate attributes of Brainstormer.
        """
        try:

            # We populate the attributes of the brainstormer with data
            # from its originating template.
            template = kwargs.pop('template')
            self.name = Truncator(template.name).chars(100, truncate='(...)')
            self.activity = template.activity
            self.sort_order = template.sort_order

        except KeyError:

            # No originating template?
            pass

        finally:

            # We can now save the brainstormer...
            super().save(*args, **kwargs)

            # ... create a default group for it...
            BrainstormerPropositionGroup.objects.get_or_create(
                brainstormer=self, **DEFAULT_GROUP_ATTRIBUTES
            )

            # ... soft-delete the groups if the brainstormer is soft-deleted
            if self.is_deleted:
                for group in self.groups.all():
                    group.is_deleted = self.is_deleted
                    group.save()

    def get_propositions(self):
        """Get the list of proposals for this brainstormer

        The QuerySet prefetch the "likes" for each proposal.

        Returns
        -------
        BrainstormerPropositionQuerySet
            Return all the propositions made during a brainstormer session.

        """
        return BrainstormerProposition.objects.filter(
            group__in=self.get_groups()
        ).prefetch_related(
            'likes'
        ).non_deleted()

    def get_groups(self):
        """Get the groups fro this brainstormer

        Returns
        -------
        BrainstormerPropositionQuerySet
            Return all the proposition groups linked to this brainstormer.

        """
        return self.groups.non_deleted()

    def get_grouped_propositions(self):
        """
        Returns
        -------
        dict
            Return all the propositions made during a brainstormer session,
            organized according to the group they belong to.

        """
        grouped_propositions = {}
        for group in self.get_groups():
            grouped_propositions[group] = group.propositions.non_deleted()
        return grouped_propositions

    def get_detail_url(self):
        """Get the url to view this brainstormer

        Returns
        -------
        string
            The wanted url.

        """

        return reverse('brainstormers:detail', kwargs={'brainstormer_uuid': self.pk})

    def get_update_url(self):
        """Get the url to update this brainstormer

        Returns
        -------
        string
            The wanted url.

        """

        return reverse('brainstormers:update', kwargs={'brainstormer_uuid': self.pk})

    def get_delete_url(self):
        """Get the url to delete this brainstormer

        Returns
        -------
        string
            The wanted url.

        """

        return reverse('brainstormers:delete', kwargs={'brainstormer_uuid': self.pk})

    def get_export_xls_url(self):
        """Get the url to export this brainstormer to an xls file

        Returns
        -------
        string
            The wanted url.

        """

        return reverse('brainstormers:brainstormer-export-xls',
                       kwargs={'brainstormer_uuid': self.pk})

    def get_export_csv_url(self):
        """Get the url to export this brainstormer to an csv file

        Returns
        -------
        string
            The wanted url.

        """

        return reverse('brainstormers:export_csv', kwargs={'brainstormer_uuid': self.pk})


class BrainstormerPropositionGroup(BrokerHookMixin, Orderable, Syncable):
    """ Group Brainstormer propositions.

    Attributes
    ----------
    brainstormer : django ``ForeignKey``
        The brainstormer this group belongs to.
    name : django ``CharField``
        A short name for this group.
    color : django ``CharField``
        The color associated with this group.
    coord_x : django ``DecimalField``
        The position of this group on an x-axis. Useful on iOS.
    coord_y : django ``DecimalField``
        The position of this group on an y-axis. Useful on iOS.

    Notes
    -----
    This model is ``Syncable`` and ``Orderable``.
    """

    brainstormer = models.ForeignKey('Brainstormer', related_name='groups')
    name = models.CharField(max_length=50, blank=True, verbose_name=_('name'))
    color = models.CharField(max_length=30, blank=True, verbose_name=_('color'))
    coord_x = models.DecimalField(max_digits=6, decimal_places=2, blank=True, default=0)
    coord_y = models.DecimalField(max_digits=6, decimal_places=2, blank=True, default=0)

    objects = BrainstormerPropositionGroupManager.from_queryset(
        BrainstormerPropositionGroupQuerySet)()

    class Meta:
        ordering = ['sort_order']

    def __str__(self):
        return '%s' % self.name

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        # Archiving propositions if group is archived
        if self.is_deleted:
            for proposition in self.propositions.all():
                proposition.is_deleted = self.is_deleted
                proposition.save()

    def is_default_group(self):
        """
        Returns
        -------
        bool
            Return True if this group is a default group, False otherwise.
        """
        return self.name == DEFAULT_GROUP_ATTRIBUTES.get('name')

    def get_create_proposition_url(self):
        """Get the url to create proposition in this brainstormer group

        Returns
        -------
        string

        """

        return reverse('brainstormers:create_proposition', kwargs={
            'brainstormer_uuid': self.brainstormer_id,
            'group_uuid': self.pk,
        })

    def get_broker_topic(self):
        """Get the broker topic to publish changes of this model

        Returns
        -------
        str

        """

        return '%s/brainstormers/%s/groups' % (
            self.brainstormer.session_id,
            self.brainstormer_id
        )

    def get_serialized_representation(self):
        """Get the serialized representation of the current proposition

        Returns
        -------
        dict

        """

        # Avoid circular imports
        from mtp.apps.brainstormers.api.serializers import (
            BrainstormerSimplePropositionGroupSerializer
        )

        return BrainstormerSimplePropositionGroupSerializer(self).data


class BrainstormerProposition(BrokerHookMixin, Likeable, Orderable, Syncable, TimeStampedModel):
    """ Brainstormer proposition arrange by group.

    Attributes
    ----------
    text : django ``CharField``
        Text of the proposition
    user : django ``ForeignKey``
        The trainee who made this proposition.
    group : doajgo ``ForeignKey``
        The group this proposition belongs to.

    Notes
    -----
    This model is ``Syncable``, ``Orderable`` and ``TimeStamped``.

    """

    class Meta:
        ordering = ['sort_order']

    text = models.CharField(max_length=100, verbose_name=_('text'))
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'))
    group = models.ForeignKey('BrainstormerPropositionGroup',
                              related_name='propositions')

    objects = BrainstormerPropositionManager.from_queryset(
        BrainstormerPropositionQuerySet)()

    def __str__(self):
        return '%s' % self.text

    def get_broker_topic(self):
        """Get the broker topic to publish changes of this model

        Returns
        -------
        str

        """

        brainstormer = self.group.brainstormer

        return '%s/brainstormers/%s/propositions' % (
            brainstormer.session_id,
            brainstormer.id
        )

    def get_serialized_representation(self):
        """Get the serialized representation of the current proposition

        Returns
        -------
        dict

        """

        # Avoid circular imports
        from mtp.apps.brainstormers.api.serializers import BrainstormerPropositionSerializer

        return BrainstormerPropositionSerializer(self).data
