"""This module provides the ``Brainstorming``, ``BrainstormingTag`` and ``BrainstormingIdea``
models for the ``ideas`` app"""

import pandas as pd

from django.core.urlresolvers import reverse
from django.db import models
from django.utils.translation import ugettext_lazy as _

from orderable.models import Orderable

from mtp.apps.broker_models_hooks.models import BrokerHookMixin
from mtp.apps.data_export.constants import EXCEL_STYLE_CHOICES
from mtp.apps.sync.models import Syncable
from mtp.apps.trainings.models import (
    LiveDependencyMixin,
    Training,
    TrainingActivity,
    TrainingSession
)

from mtp.discrete_apps.versionable.models import Dependency

from ..managers import (
    BrainstormingIdeaManager,
    BrainstormingTagManager,
    IdeasActivityManager,
)

from .ideas import Idea, IdeasActivity


class Brainstorming(TrainingActivity, IdeasActivity):
    """Django model defining a "brainstorming" activity

    This model does not contains any fields, besides those provided by ``TrainingActivity``,
    but the ``BrainstormingTag`` and ``BrainstormingIdea`` below are linked to this model.

    """

    verbose_activity_name = _('Brainstorming')

    objects = IdeasActivityManager()

    @classmethod
    def get_create_url(cls):
        """Get the url to create a new ``Brainstorming`` instance

        Returns
        -------
        string

        """

        return reverse('ideas:brainstorming-create')

    @classmethod
    def get_training_create_url(cls, training):
        """Get the url to create a new ``Brainstorming`` instance in the provided training

        Parameters
        ----------
        training: Training
            The training we want to create a new ``Brainstorming`` for.

        Returns
        -------
        string

        """

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

    def get_absolute_url(self):
        """Get the absolute url of the current ``Brainstorming``

        It actually returns the edit url, as there is no "detail" view of a ``Brainstorming``

        Returns
        -------
        string

        """

        return self.get_detail_url()

    def get_detail_url(self):
        """Get the url to view the current ``Brainstorming``

        Returns
        -------
        string

        """

        return reverse('ideas:brainstorming-detail', kwargs={'brainstorming_uuid': self.pk})

    def get_edit_url(self):
        """Get the url to edit the current ``Brainstorming``

        Returns
        -------
        string

        """

        return reverse('ideas:brainstorming-update', kwargs={'brainstorming_uuid': self.pk})

    def get_results_excel_data(self, session):
        """Get the results data of this brainstorming for a given session

        Parameters
        ----------
        session: TrainingSession
            The training session we want the results for

        Returns
        -------
        dict
            A dict containing the parameters for the ``ExcelDataExporter.add_sheet``

        """

        # Fetch the ideas and the tags related to this brainstorming
        ideas = self.ideas.filter(
            models.Q(session=session) | models.Q(session=None)
        ).non_deleted().order_by('created')
        tags = self.tags.filter(
            models.Q(session=session) | models.Q(session=None)
        ).non_deleted().order_by('-sort_order')

        # Build the columns of the data frame
        columns = [
            str(_('Likes')),
            str(_('Author first name')),
            str(_('Author last name')),
        ]

        header_styles = [EXCEL_STYLE_CHOICES.HEADER] * len(columns)

        # Insert on column for each tag, just after the "Likes" column
        for index, tag in enumerate(tags, start=1):
            # Make sure the column name is unique
            column_name = 'Tag %d: %s' % (index, tag.name)

            columns.insert(index, column_name)
            header_styles.insert(index, EXCEL_STYLE_CHOICES.HEADER)

        data_frame = pd.DataFrame(columns=columns)

        # For each idea, populate a row of the data frame
        for index, idea in enumerate(ideas):
            idea_data = [
                idea.likes.non_deleted().count(),
                idea.user.first_name if idea.user else '',
                idea.user.last_name if idea.user else ''
            ]

            # Insert the tags data after the "Likes" column
            idea_data[1:0] = [
                'x' if tag in idea.tags.non_deleted() else ''
                for tag in tags
            ]

            # The text of the proposition is the index
            index_name = '%d - %s' % (
                index,
                idea.text
            )

            data_frame.loc[index_name] = idea_data

        return {
            'data': data_frame,
            'header_styles': header_styles,
            'freeze_panes': [1, 1]
        }


class BrainstormingTag(BrokerHookMixin, Syncable, Orderable, LiveDependencyMixin, Dependency):
    """Django model defining a brainstorming tag

    The tags are used to categorize the ``Idea``s into a ``Brainstorming`` activity.

    Attributes
    ----------
    name: django ``CharField`` field
        The name of this tag
    color: django ``CharField`` field
        The color of this tag, can be empty.
    brainstorming: django ``ForeignKey`` field to ``Brainstorming``
        The brainstorming this tag belongs to.
    session: django ``ForeignKey`` field to ``TrainingSession``, Nullable
        The session this tag belongs to, if applicable

    """

    name = models.CharField(
        max_length=50,
        verbose_name=_('Name')
    )
    color = models.CharField(
        max_length=30,
        blank=True,
        null=True,
        verbose_name=_('Color')
    )
    brainstorming = models.ForeignKey(
        Brainstorming,
        related_name='tags',
        verbose_name=_('Brainstorming'),
        on_delete=models.CASCADE
    )
    session = models.ForeignKey(
        TrainingSession,
        related_name='brainstorming_tags',
        null=True,
        blank=True,
        verbose_name=_('Session'),
        on_delete=models.CASCADE
    )

    versionable_link = (Training, 'brainstorming__training')
    non_lockable_fields = Dependency.non_lockable_fields + Syncable.non_lockable_fields

    objects = BrainstormingTagManager()

    class Meta:
        unique_together = ['brainstorming', 'session', 'sort_order']

    def should_be_sent_to_broker(self, event_type):
        """Check if this tag changes should be sent to the broker

        We only want to publish to the broker the tags linked to a session.

        For the parameters, see ``BaseBrokerHook``

        """

        return bool(self.session_id)

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

        Returns
        -------
        str

        """

        return '%s/brainstormings/%s/tags' % (
            self.session_id,
            self.brainstorming_id
        )

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

        Returns
        -------
        dict

        """

        # Avoid circular imports
        from mtp.apps.ideas.apiv2.serializers import (
            BrainstormingTagSerializer
        )

        return BrainstormingTagSerializer(self).data


class BrainstormingIdea(BrokerHookMixin, Syncable, Idea, LiveDependencyMixin, Dependency):
    """Django model defining a "brainstorming" idea

    Attributes
    ----------
    text: django ``CharField`` field. Maximum 80 chars.
        The textual content of this idea.
    tags: django ``ManyToManyField`` field to ``Tag``.
        The tags associated with this idea.
    brainstorming: django ``ForeignKey`` field to ``Brainstorming``.
        The brainstorming this idea was proposed into.

    """

    text = models.CharField(
        max_length=80,
        verbose_name=_('Text')
    )
    tags = models.ManyToManyField(
        BrainstormingTag,
        related_name='ideas',
        verbose_name=_('Tags')
    )
    brainstorming = models.ForeignKey(
        Brainstorming,
        related_name='ideas',
        verbose_name=_('Brainstorming'),
        on_delete=models.CASCADE
    )

    versionable_link = (Training, 'brainstorming__training')
    non_lockable_fields = Dependency.non_lockable_fields + Syncable.non_lockable_fields

    objects = BrainstormingIdeaManager()

    class Meta:
        unique_together = ['brainstorming', 'session', 'sort_order']

    def should_be_sent_to_broker(self, event_type):
        """Check if this idea changes should be sent to the broker

        We only want to publish to the broker the ideas linked to a session.

        For the parameters, see ``BaseBrokerHook``

        """

        return bool(self.session_id)

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

        Returns
        -------
        str

        """

        return '%s/brainstormings/%s/ideas' % (
            self.session_id,
            self.brainstorming_id
        )

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

        Returns
        -------
        dict

        """

        # Avoid circular imports
        from mtp.apps.ideas.apiv2.serializers import BrainstormingIdeaSerializer

        return BrainstormingIdeaSerializer(self).data
