"""This module contains the api-related views of the ``evaluations`` app"""

import json

from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

from mtp.apps.accounts.models import MTPUser
from mtp.apps.api.constants import EMPTY_HTTP_RESPONSE
from mtp.apps.trainings.models import TrainingSession

from ..models import (
    Criterion as EvaluationCriterion,
    Evaluation,
    Exercise as EvaluationExercise,
    Rating as EvaluationRating,
)


def _evaluation_save_criteria(request, user_uuid, criteria_type):
    """Record all transmitted evaluation criteria in the database.

    Parameters
    ----------
    request : django ``HttpRequest``
        The transmitted HTTP request.
    user_id : str
        The UUID of the user being evaluated.
    criteria_type : str
        The type of the criteria being processed.
        One of the following:
         - ``Criterion.CRITERION_TYPES.QUALITATIVE``
         - ``Criterion.CRITERION_TYPES.QUANTITATIVE``

    Returns
    -------
    A Django ``HttpResponse``.

    Notes
    -----
    The ``POST`` method is enforced by the ``require_POST``
    decorator of the ``evaluation_save_criteria_*`` entry points.

    """

    # The ratings are transmitted via a ``json`` field.
    if 'json' not in request.POST:
        return EMPTY_HTTP_RESPONSE

    # We probably need a trainee...
    try:
        trainee = MTPUser.objects.get(pk=user_uuid)
    except MTPUser.DoesNotExist:
        return EMPTY_HTTP_RESPONSE

    if "exercice_id" in request.POST:

        # ... and a few other objects.
        session = get_object_or_404(TrainingSession, pk=request.POST['session_id'])
        exercise = get_object_or_404(EvaluationExercise, pk=request.POST['exercice_id'])
        judge = get_object_or_404(MTPUser, pk=request.POST['trainer_id'])

        # We start by deleting any previously recorded rating for this
        # (session, exercise, criterion_type, judge, trainee) tuple.
        EvaluationRating.objects.filter(
            session=session, judge=judge, trainee=trainee,
            criterion__exercise=exercise,
            criterion__criterion_type=criteria_type,
        ).delete()

        # We can now record the transmitted ratings.
        types = EvaluationCriterion.CRITERION_TYPES
        submitted_ratings = json.loads(request.POST['json'])['data']

        for submitted_rating in submitted_ratings:

            # We extract the criterion identifier from the transmitted json...
            criterion_id = submitted_rating['id']

            # ... and save its actual value in the proper field,
            # according to its type.
            if criteria_type == types.QUANTITATIVE:
                EvaluationRating.objects.create(
                    judge=judge, trainee=trainee, session=session,
                    criterion_id=criterion_id, score=submitted_rating['value'],
                )

            elif criteria_type == types.QUALITATIVE:
                for rating_item in submitted_rating.get('items'):
                    EvaluationRating.objects.create(
                        judge=judge, trainee=trainee, session=session,
                        criterion_id=criterion_id, text=rating_item.get('text'),
                    )

    # All done!
    return EMPTY_HTTP_RESPONSE


@require_POST
@csrf_exempt
def evaluation_save_criteria_num(request, user_uuid):
    """Record all transmitted numerical evaluation criteria in the database.

    Notes
    -----
    This is preserved for backwards compatibility with the current API.
    It now merely calls the ``_evaluation_save_criteria`` function.

    See also
    --------
    The docstring of the ``_evaluation_save_criteria`` function above.

    """
    return _evaluation_save_criteria(
        request, user_uuid,
        EvaluationCriterion.CRITERION_TYPES.QUANTITATIVE,
    )


@require_POST
@csrf_exempt
def evaluation_save_criteria_text(request, user_uuid):
    """Record all transmitted textual evaluation criteria in the database.

    Notes
    -----
    This is preserved for backwards compatibility with the current API.
    It now merely calls the ``_evaluation_save_criteria`` function.

    See also
    --------
    The docstring of the ``_evaluation_save_criteria`` function above.

    """
    return _evaluation_save_criteria(
        request, user_uuid,
        EvaluationCriterion.CRITERION_TYPES.QUALITATIVE,
    )


def evaluation_get_result(request, eval_uuid, user_uuid=None):
    """Return graphs representing a trainee's results in an evaluation.

    Notes
    -----
    This is a reimplementation of a previous version, hastily adapted
    to use refactored data models...

    """

    evaluation = get_object_or_404(Evaluation, pk=eval_uuid)
    if user_uuid and user_uuid != '0':
        trainee = get_object_or_404(MTPUser, pk=user_uuid)
    else:
        trainee = None
    session = get_object_or_404(TrainingSession, pk=request.GET["session_id"])

    graphs = []
    criteria = []

    for exercise in evaluation.exercises.non_deleted():
        graph = {
            'name': exercise.name,
            'start_range': exercise.start_range,
            'end_range': exercise.end_range,
            'step': exercise.step,
            'colors': ['OxFF0000', 'Ox00FF00', 'Ox0000FF', 'OxFF00FF', 'OxFFFF00', 'Ox00FFFF'],
            'attributes': []
        }

        for criterion in exercise.criteria.quantitative().non_deleted():

            ratings = criterion.ratings.filter(session=session)
            if trainee:
                ratings = ratings.filter(trainee=trainee)
            count = 0
            score = 0

            for rating in ratings:
                count += 1
                score += rating.score

            axis_attributes = {
                criterion.description: [
                    exercise.end_range,
                    (count and float(score) / count) or exercise.start_range,
                ]
            }
            graph['attributes'].append(axis_attributes)

        # Do't show the graph if we get less than 2 axes
        if len(graph['attributes']) > 2:
            graphs.append(graph)

        for criterion in exercise.criteria.qualitative().non_deleted().order_by('sort_order'):

            criterion_json = {
                'label': criterion.description,
                'order': criterion.sort_order,
                'id': criterion.id,
                'items': [],
                'type': 'text'
            }
            ratings = criterion.ratings.filter(session=session)
            if trainee:
                ratings = ratings.filter(trainee=trainee)
            for rating in ratings:
                rating_json = {
                    'text': rating.text,
                    'id': rating.id
                }
                criterion_json['items'].append(rating_json)

            criteria.append(criterion_json)

    result = {
        'graphs': graphs,

        # Looks like these two keys may be expected by the iOS app.
        # Let's keep them for now... TODO: Make sure.
        'percent': [],
        'criterias': criteria,  # sic
    }

    return JsonResponse({'spider_graph': result})
