"""This module holds the api-related views of the ``trainings`` app"""

from itertools import chain
import json

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import BooleanField, Case, Q, Value, When
from django.http import Http404
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.utils.translation import pgettext

from rest_framework import generics, status
from rest_framework.generics import ListCreateAPIView
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView

from mtp.apps.accounts.constants import INTERNAL_GROUP_TRAINEE
from mtp.apps.accounts.api.serializers import UserSerializer
from mtp.apps.accounts.models import GroupRole
from mtp.apps.api.authentication import CsrfExemptSessionAuthentication
from mtp.apps.documents.serializers import NoteSerializer
from mtp.apps.brainstormers.models import Brainstormer, BrainstormerActivity
from mtp.apps.common.message_broker import send_data_to_topic, MESSAGE_BROKER_TOPICS
from mtp.apps.documents.models import BackgroundExecution, Document, Note
from mtp.apps.evaluations.models import Evaluation
from mtp.apps.metaplans.models import Metaplan
from mtp.apps.roleplays.models import Roleplay
from mtp.apps.surveys.models import Survey
from mtp.apps.workshops.models import Workshop
from mtp.apps.workshops.serializers import CompatibilityWorkshopSerializer

from ..models import Training, TrainingSession

from .serializers import (
    TrainingSerializer, TrainingSessionReadSerializer,
    TrainingSessionWriteSerializer, SessionChallengeScoreSerializer
)


class SessionList(ListCreateAPIView):
    """
    List all sessions, or create a new session.

    Notes
    -----
    We add the annotate before testing the type of user
    because the serializer is expecting the attribute
    `is_attendee`` even for the `trainers`.

    The trainer see only his own `TrainingSession`.
    The trainee see the session he's in and the
    session who are not `restricted`

    """
    serializer_class = TrainingSessionReadSerializer

    def get_queryset(self):
        user = self.request.user
        sessions_qs = TrainingSession.objects.annotate(
            is_attendee=Case(
                When(users=None, then=Value(False)),
                When(users__in=[user], then=Value(True)),
                default=Value(True),
                output_field=BooleanField()
            )
        )

        # We need this to tranform the `INNER JOIN` made by the
        # annotate to a `LEFT OUTER JOIN`.
        # We do that to avoid excluding sessions without trainees.
        sessions_qs.query.promote_joins(
            [TrainingSession.users.through._meta.db_table]
        )

        # Exclude the closed ones
        sessions_qs = sessions_qs.exclude(state=TrainingSession.STATE_CHOICES.CLOSED)

        if user.is_staff:
            # Since this query is annotated with a LEFT JOIN, we need a ``distinct`` to avoid
            # potential duplicates.
            return sessions_qs.filter(trainer=user).distinct()
        else:
            sessions_qs = sessions_qs.exclude(state=TrainingSession.STATE_CHOICES.NOT_OPEN).filter(
                    ~Q(permissions=TrainingSession.PERMISSION_CHOICES.RESTRICTED) | Q(users=user)
                ).distinct()

            return sessions_qs

    def perform_create(self, serializer):
        serializer.save(trainer=self.request.user)

    def create(self, request):

        user = request.user
        if not user.is_staff:
            return Response({}, status=status.HTTP_403_FORBIDDEN)

        serializer = TrainingSessionWriteSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(trainer=request.user)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class SessionDetail(APIView):
    """Retrieve a single session instance"""

    def get_training_session(self, pk):
        """Retrieve the training session from its PK

        Parameters
        ----------
        pk: str
            The PK of the training session to retrieve

        Returns
        -------
        ``mtp.apps.trainings.models.TrainingSession``

        Raises
        ------
        Http404
            If the training session cannot be found

        """

        try:
            return TrainingSession.objects.get(pk=pk)
        except TrainingSession.DoesNotExist:
            raise Http404

    # pylint: disable=unused-argument,redefined-builtin
    def get(self, request, session_uuid, format=None):
        """Serialize the training session and return it"""

        session = self.get_training_session(session_uuid)
        serializer = TrainingSessionReadSerializer(session)

        return Response(serializer.data)

    def put(self, request, session_uuid, format=None):
        """Update a ``TrainingSession``

        Parameters
        ----------
        session_uuid: str
            The PK of the training session to update

        Returns
        -------
        ``Response``
            An Http response with the updated and serialized ``TrainingSession``

        """

        session = self.get_training_session(session_uuid)
        if session.trainer != request.user:
            return Response(status=status.HTTP_403_FORBIDDEN)

        if "users" not in request.data:
            users = session.users.all()
        else:
            users = None

        serializer = TrainingSessionWriteSerializer(
            session, data=request.data, partial=True
        )
        if serializer.is_valid():
            if users:
                serializer.save(users=users)
            else:
                serializer.save()
            return Response(serializer.data)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class SessionUGC(APIView):
    """Retrieve contents generated by user"""

    def get_object(self, pk):
        try:
            return TrainingSession.objects.get(pk=pk)
        except TrainingSession.DoesNotExist:
            raise Http404

    def get(self, request, session_uuid, format=None):
        session = self.get_object(session_uuid)

        notes = NoteSerializer(
            Note.objects.filter(user=request.user, session=session),
            many=True
        )

        ugc = {
            "notes": notes.data,
        }

        response = Response(
            ugc,
            status=status.HTTP_200_OK
        )
        return response


class SessionContent(APIView):
    """
    Retrieve a session content
    """

    template_name = ""

    def get_object(self, pk):
        try:
            return TrainingSession.objects.get(pk=pk)
        except TrainingSession.DoesNotExist:
            raise Http404

    def convert_survey_to_workshop(self, survey_data):
        return [{
            'title': pgettext(context='Title for API', message='Survey'),
            'has_score': False,
            'order': 1,
            'questions': [
                {
                    'options': [],
                    'label': question['title'],
                    'description': '',  # not used but entry is expected
                    'order': question_index + 1,
                    'id': question['id'],
                    'type': 'quizz',
                    'is_multiple_choices': question['type'] == 'MULTIPLE_CHOICES',
                    '_real_type': question['type'],
                    'answers': [
                        {
                            'points': 0,
                            'order': choice_index + 1,
                            'id': choice['id'],
                            'label': choice['label'],
                            'good': False,
                            'color': '',
                        }
                        for choice_index, choice in enumerate(question['choices'])
                    ],

                }
                for question_index, question in enumerate(chain.from_iterable(
                    (pool['questions'] for pool in survey_data['pools'])
                ))
            ],
            'groups': [INTERNAL_GROUP_TRAINEE],
            'id': survey_data['id'],
            'type': 'survey',
            'module': 'survey',
            'preview': 'workshop-preview.png',
        }]

    # FIXME : mettre une explication du fixme
    def get(self, request, session_uuid, format=None):
        session = self.get_object(session_uuid)
        # Metaplan Version
        version = request.GET.get('version', None)
        # Fixme: remove the line below line when old api is deprecated
        current_session = session
        training = session.training
        trainer = session.trainer

        workshops = Workshop.objects.filter(training=training).non_deleted()
        serialized_workshop = CompatibilityWorkshopSerializer(workshops, many=True)
        workshops_json = JSONRenderer().render(serialized_workshop.data)

        survey, __ = Survey.objects.get_or_create_for_training(
            session.training, session.trainer
        )

        survey_json = json.dumps(
            self.convert_survey_to_workshop(survey.serialize()),
            cls=DjangoJSONEncoder
        ) if survey else None

        documents = Document.objects.filter(training=training).non_deleted()
        documents_objects = chain.from_iterable(
            document.compatibility_serialize()
            for document in documents
        )
        documents_json = JSONRenderer().render(documents_objects)

        downloads = []

        evaluations = Evaluation.objects.filter(
            training=training, is_deleted=False, hidden=False
        )
        bg_tasks = BackgroundExecution.objects.all()

        metaplans = Metaplan.objects.for_training_session(session).non_deleted()

        actions = training.action_set.filter(user=trainer, training=training)

        message_broker_settings = {
            'url': settings.MESSAGE_BROKER_URL,
            'port': settings.MESSAGE_BROKER_PORT,
        }

        brainstormers = Brainstormer.objects.for_training_session(session).non_deleted()

        activities = BrainstormerActivity.objects.non_deleted()

        roleplays = Roleplay.objects.non_deleted().filter(training=training)

        return render_to_response(
            'api/json/current.json', locals(),
            content_type='application/json',
            context_instance=RequestContext(request),
        )
    # EMXIF


class TrainingList(generics.ListAPIView):
    """List all trainings the current trainer can access.

    Notes
    -----
    We only want the favorites trainings to be returned.

    See also
    --------
    ``mtp.apps.trainings.models.TrainingQueryset``

    """

    serializer_class = TrainingSerializer

    def get_queryset(self):
        return Training.objects.non_deleted().available_for_user(self.request.user)


class SessionUserList(APIView):
    """List all session's Users or add a new one."""

    authentication_classes = [CsrfExemptSessionAuthentication]

    def get_object(self, pk):
        try:
            return TrainingSession.objects.get(pk=pk)
        except TrainingSession.DoesNotExist:
            raise Http404

    def get_user(self, pk):
        try:
            return get_user_model().objects.get(pk=pk)
        except get_user_model().DoesNotExist:
            raise Http404

    def get(self, request, session_uuid, format=None):
        """Get the list of users of this ``TrainingSession``

        Parameters
        ----------
        session_uuid: str
            The UUID of the session we want the users from

        Returns
        -------
        ``Response``
            An http response containing a serialized user list

        Notes
        -----
        An explanation about the "WTF Zone" comment below:

        - Someone added this hack in the first place
        - Someone came and removed this hack
        - Someone re-added this hack on the 2.5
        - Someone want the hack back on the 3.0 also

        "Legacy API, my ass !"
        © Metamatik 2015

        """

        session = self.get_object(pk=session_uuid)
        users = list(session.users.non_deleted())

        # WTF Zone
        users.append(session.trainer)

        serializer = UserSerializer(users, many=True)
        return Response(serializer.data)

    def post(self, request, session_uuid, format=None):
        """
        Check if user belong to the session. Else, add the user
        """

        user = request.user
        session = self.get_object(pk=session_uuid)
        # add comment and put it in TrainingSession model method
        if user.is_staff:
            if session.trainer != user:
                return Response({}, status=status.HTTP_403_FORBIDDEN)
            if "user_id" not in request.data:
                return Response({}, status=status.HTTP_400_BAD_REQUEST)

            user_to_add = self.get_user(pk=request.data["user_id"])
            session.users.add(user_to_add)
            for group in session.training.default_group_set.mgroups.exclude(
                    pk__in=user_to_add.mgroups.values_list('pk', flat=True)):
                GroupRole.objects.create(user=user_to_add, group=group)
            session.save()
            user_to_add.save()
            send_data_to_topic(json.dumps(
                {
                    "action": "join",
                    "module_name": "waiting_room",
                    "module_id": "",
                    "sender_id": session.trainer.id,
                    "recepient_id": user_to_add.id,
                    "session_id": session.id,
                    "first_name": user.first_name,
                    "last_name": user.last_name,
                    "training_name": session.training.name,
                }
            ), MESSAGE_BROKER_TOPICS["waitingroom"])

            return Response({})
        else:
            if user in session.users.all():
                return Response({})
            if session.permissions == TrainingSession.PERMISSION_CHOICES.OPEN:
                session.users.add(user)
                # We cannot use ``bulk_create`` because it does not call the ``save`` method and the
                # ``Syncable`` models need to call the ``save`` method for each update.
                for group in session.training.default_group_set.mgroups.exclude(
                        pk__in=user.mgroups.values_list('pk', flat=True)):
                    GroupRole.objects.create(user=user, group=group)
                session.save()
                user.save()
                return Response({})
            elif session.permissions == TrainingSession.PERMISSION_CHOICES.APPROVAL_NEEDED:
                send_data_to_topic(json.dumps(
                    {
                        "action": "join_request",
                        "module_name": "waiting_room",
                        "module_id": "",
                        "sender_id": user.id,
                        "recepient_id": session.trainer.id,
                        "session_id": session.id,
                        "first_name": user.first_name,
                        "last_name": user.last_name,
                        "training_name": session.training.name
                    }
                ), MESSAGE_BROKER_TOPICS["trainer"](session.trainer.id))

                return Response({}, status=status.HTTP_402_PAYMENT_REQUIRED)
            else:
                return Response({}, status=status.HTTP_403_FORBIDDEN)


class SessionChallengeScoreListView(APIView):
    """Get the challenge scores of the given training session

    Example
    -------
    The data is returned in that format:

    >>> [
    ...     {
    ...     'score': 0,
    ...     'trainee': {
    ...         'id': '3df8a6b6-e8e6-4956-820c-0e0067a0fec5',
    ...         'username': 'trainee-02',
    ...         'first_name': 'Trai-02',
    ...         'last_name': 'Nee-02',
    ...         'email': "trainee-02@magency.fr",
    ...         'natural_language': "en",
    ...         'photo': None,
    ...         'photos': [],
    ...         'groups': [ 'trainee' ]
    ...        }
    ...     }
    >>> ]

    See also
    --------
    ``mtp.apps.trainings.models.TrainingSession.debrief_challenge_scores`` method.

    """

    def get(self, request, session_uuid):
        """Get the score, serialize them and return the result"""

        training_session = get_object_or_404(TrainingSession, pk=session_uuid)

        challenge_scores = training_session.debrief_challenge_scores()
        serialized_challenge_scores = SessionChallengeScoreSerializer(
            challenge_scores, many=True)

        return Response(serialized_challenge_scores.data)
