"""This module contains the managers for the ``ideas`` app"""

from django.conf import settings
from django.db import models

from mtp.apps.sync.managers import SyncableQuerySet
from mtp.apps.trainings.managers import TrainingActivityManager

from mtp.discrete_apps.versionable.managers import (
    DependencyManager,
    DependencyQuerySet
)


class IdeasActivityManager(TrainingActivityManager):
    """Manager for the ``IdeasActivity`` model"""

    use_in_migrations = True

    def migrate_brainstormer_templates(self):
        """Migrate the existing ``BrainstormerTemplate`` models to their ideas equivalent"""

        from mtp.apps.ideas.models import (
            Brainstorming,
            QuestionWall,
            WordCloud
        )
        from mtp.apps.brainstormers.constants import BRAINSTORMER_TYPE_CHOICES
        from mtp.apps.brainstormers.models import BrainstormerTemplate

        ideas_models = {
            BRAINSTORMER_TYPE_CHOICES.PROPOSITIONWALL: Brainstorming,
            BRAINSTORMER_TYPE_CHOICES.QUESTIONWALL: QuestionWall,
            BRAINSTORMER_TYPE_CHOICES.WORDCLOUD: WordCloud
        }

        # Store the values of the templates as a list
        template_values_list = list(BrainstormerTemplate.objects.values(
            'id',
            'name',
            'description',
            'brainstormer_type',
            'sort_order',
            'training_id',
            'is_deleted'
        ))

        # Delete all the templates
        BrainstormerTemplate.objects.all().delete()

        for template_values in template_values_list:
            # Create the ``IdeasActivity`` sub-model depending of the "model" key
            brainstormer_type = template_values.pop('brainstormer_type')

            idea_instance = ideas_models[brainstormer_type](
                **template_values
            )

            # If we are on a slave instance, bypass the sync stuff
            if settings.INSTANCE_IS_SLAVE:
                idea_instance.save(mtp_update=True)
            else:
                idea_instance.save()

    def migrate_brainstormers(self):  # pylint: disable=too-many-locals
        """Migrate the existing ``Brainstormer`` models to their ideas equivalent

        The steps we are following are:
        - Store a list of values of the ``Brainstormer`` and the related models
        - Delete the ``Brainstormer``
        - Create the ``IdeasActivity`` depending of the "brainstormer_type"
        - Create the related ``BrainstormingTag``
        - Create the related ``Idea`` sub-model depending of the "brainstormer_type"

        See Also
        --------
        ``migrate_brainstormer_groups`` and ``migrate_brainstormer_propositions`` methods.

        """

        from mtp.apps.brainstormers.constants import BRAINSTORMER_TYPE_CHOICES
        from mtp.apps.brainstormers.models import Brainstormer, BrainstormerPropositionGroup
        from mtp.apps.ideas.models import (
            Brainstorming,
            QuestionWall,
            WordCloud
        )
        from mtp.apps.likes.models import Like

        ideas_models = {
            BRAINSTORMER_TYPE_CHOICES.PROPOSITIONWALL: Brainstorming,
            BRAINSTORMER_TYPE_CHOICES.QUESTIONWALL: QuestionWall,
            BRAINSTORMER_TYPE_CHOICES.WORDCLOUD: WordCloud
        }

        while True:  # Avoid filling the memory with too many objects
            brainstormers = Brainstormer.objects.all()[:100]

            if len(brainstormers) == 0:
                break

            for brainstormer in brainstormers:
                brainstormer_values = {
                    'id': brainstormer.id,
                    'name': brainstormer.name,
                    'description': brainstormer.description,
                    'sort_order': brainstormer.sort_order,
                    'is_anonymous': brainstormer.anonymous,
                    'session_id': brainstormer.session_id,
                    'is_deleted': brainstormer.is_deleted
                }

                proposition_groups_values = list(brainstormer.groups.values(
                    'id',
                    'name',
                    'color',
                    'is_deleted'
                ))

                propositions_values = list(brainstormer.propositions.values(
                    'id',
                    'text',
                    'user_id',
                    'is_deleted',
                    'created',
                    'modified'
                ))

                for proposition in propositions_values:
                    proposition['groups'] = list(
                        BrainstormerPropositionGroup.objects.filter(
                            propositions__in=[proposition['id']]
                        ).values_list('id', flat=True)
                    )

                    propositions_likes = Like.objects.filter(
                        object_uuid=proposition['id']
                    ).values(
                        'id',
                        'author_id',
                        'weight',
                        'date_time',
                        'is_deleted',
                    )

                    proposition['likes'] = list(propositions_likes)

                brainstormer.delete()

                # Create the ``IdeasActivity`` sub-model depending of the "brainstormer_type"
                idea_activity = ideas_models[brainstormer.brainstormer_type].objects.create(
                    **brainstormer_values
                )

                # Create the tags
                self.migrate_brainstormer_groups(
                    idea_activity,
                    proposition_groups_values
                )

                # Create the propositions
                self.migrate_brainstormer_propositions(
                    idea_activity,
                    propositions_values
                )

    def migrate_brainstormer_groups(self, brainstorming, group_values_list):
        """Migrate the existing ``PropositionGroup`` models to their ideas equivalent

        Parameters
        ----------
        brainstorming: Brainstorming
            The Brainstorming the group will be migrated to
        group_values_list: list(dict)
            A list of dict containing the values of the groups

        """

        from mtp.apps.ideas.models import BrainstormingTag

        for index, group_values in enumerate(group_values_list):
            BrainstormingTag.objects.create(
                **dict(
                    sort_order=index,
                    session_id=brainstorming.session_id,
                    brainstorming_id=brainstorming.id,
                    **group_values
                )
            )

    # pylint: disable=too-many-locals
    def migrate_brainstormer_propositions(self, ideas_activity, propositions_values_list):
        """Migrate the brainstormer propositions to their ideas equivalent

        Parameters
        ----------
        ideas_activity: IdeasActivity
            The Idea activity to which the ideas will be migrated
        propositions_values_list: list(dict)
            A list of dict containing the values of the propositions

        """

        from mtp.apps.ideas.models import (
            Brainstorming,
            BrainstormingIdea,
            BrainstormingTag,
            QuestionWall,
            QuestionWallIdea,
            WordCloud,
            WordCloudIdea,
        )
        from mtp.apps.likes.models import Like

        ideas_models = {
            Brainstorming: BrainstormingIdea,
            QuestionWall: QuestionWallIdea,
            WordCloud: WordCloudIdea
        }

        current_idea_model = ideas_models[ideas_activity.__class__]

        # Define the default values of the propositions we are about to create
        propositions_default_values = {
            'session_id': ideas_activity.session_id,
            ideas_activity.__class__.__name__.lower() + '_id': ideas_activity.id
        }

        for propositions_values in propositions_values_list:
            propositions_values.update(propositions_default_values)

            # Handle the groups of the propositions (the tags)
            groups_ids = propositions_values.pop('groups')

            # Handle the likes
            likes = propositions_values.pop('likes')

            idea = current_idea_model.objects.create(**propositions_values)

            for like in likes:
                Like.objects.create(
                    liked_object=idea,
                    **like
                )

            # If we are dealing with a ``Brainstorming`` instance, we need to associate
            # our newly created ``BrainstormingIdeas`` with the provided groups_ids (the tags)
            if isinstance(ideas_activity, Brainstorming) and groups_ids:
                idea.tags.add(*BrainstormingTag.objects.filter(id__in=groups_ids))


class IdeaQuerySet(SyncableQuerySet):
    """QuerySet for the models who inherit from the ``Idea`` abstract model"""

    def for_session(self, session):
        """Filter the ideas created in the given session

        Parameters
        ----------
        session: mtp.apps.trainings.models.TrainingSession
            The session for which we want the ideas

        Returns
        -------
        IdeaQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(session=session)


class IdeaManager(models.Manager.from_queryset(IdeaQuerySet)):
    """Manager for the models who inherit from the ``Idea`` abstract model"""

    pass


class BrainstormingTagQueryset(SyncableQuerySet, DependencyQuerySet):
    """QuerySet for the ``BrainstormingTag`` model"""

    def for_session(self, session):
        """Filter the tags which were created in the given session

        Parameters
        ----------
        session : mtp.apps.trainings.models.TrainingSession
            The session for which we want the tags

        Returns
        -------
        BrainstormingTagQueryset
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(session=session)


class BrainstormingTagManager(DependencyManager.from_queryset(BrainstormingTagQueryset)):
    """Manager for the ``BrainstormingTag`` model"""

    pass


class BrainstormingIdeaQueryset(DependencyQuerySet, IdeaQuerySet):
    """QuerySet for the ``BrainstormingIdea`` model"""

    pass


class BrainstormingIdeaManager(DependencyManager.from_queryset(BrainstormingIdeaQueryset)):
    """Manager for the ``BrainstormingIdea`` model"""

    pass
