"""This module holds the serializers for the ``apiv2`` module of ``assessments`` app"""

from rest_framework import serializers
from rest_framework.exceptions import APIException

from mtp.apps.accounts.apiv2.serializers import MTPUserSerializer
from mtp.apps.assessments.models import (
    AssessableActivityUserScore,
    Competency,
    CompetencyUserScore,
    CompetencyTree,
    Dimension,
    DimensionUserScore,
    Lesson,
    LessonReference,
    RecommendationLevel,
    UserLessonRecommendation,
)
from mtp.apps.trainings.models import TrainingActivity, TrainingSession

from ..models import CertificationProgramUserStatus

from .mixins import UserContextSerializerMixin, SessionContextSerializerMixin


class AssessmentSerializerException(APIException):
    """Custom exception to use throughout this module"""

    status_code = 500
    default_detail = 'An error occured while serializing an assessment.'


class ActivityScoresSerializer(UserContextSerializerMixin, SessionContextSerializerMixin,
                               serializers.ModelSerializer):
    """Serialize activity scores for a user's session"""

    original_id = serializers.CharField(source='original_version_id')
    score = serializers.SerializerMethodField()

    class Meta:
        model = TrainingActivity
        fields = (
            'id',
            'name',
            'original_id',
            'score',
        )

    def get_score(self, instance):
        """Return activity score for the current user's session"""

        try:
            return AssessableActivityUserScore.objects.get(
                user=self.user,
                activity_id=instance.id,
                session=self.session,
            ).score
        except (AssessableActivityUserScore.DoesNotExist,
                AssessableActivityUserScore.MultipleObjectsReturned):
            raise AssessmentSerializerException(
                'Can not find a suitable AssessableActivityUserScore'
            )


class DimensionScoresSerializer(UserContextSerializerMixin, SessionContextSerializerMixin,
                                serializers.ModelSerializer):
    """Serialize dimension scores for a competency"""

    original_id = serializers.CharField(source='original_version_id')
    score = serializers.SerializerMethodField()

    class Meta:
        model = Dimension
        fields = (
            'id',
            'name',
            'original_id',
            'score',
        )

    def get_score(self, instance):
        """Return dimension score for the current user's session"""

        try:
            return instance.user_scores.get(
                user=self.user,
                session=self.session,
            ).score
        except (DimensionUserScore.DoesNotExist, DimensionUserScore.MultipleObjectsReturned):
            raise AssessmentSerializerException(
                'Can not find a suitable DimensionUserScore'
            )


class CompetencyScoresSerializer(UserContextSerializerMixin, SessionContextSerializerMixin,
                                 serializers.ModelSerializer):
    """Serialize competency scores for a user's session"""

    dimension_scores = serializers.SerializerMethodField()
    original_id = serializers.CharField(source='original_version_id')
    score = serializers.SerializerMethodField()

    class Meta:
        model = Competency
        fields = (
            'dimension_scores',
            'id',
            'name',
            'original_id',
            'score',
        )

    def get_dimension_scores(self, instance):
        """Return a list of serialized dimension scores for this competency"""

        return [
            DimensionScoresSerializer(dimension, context={
                'session': self.session,
                'user': self.user,
            }).data for dimension in instance.dimensions.non_deleted()
        ]

    def get_score(self, instance):
        """Return competency score for the current user's session"""

        try:
            return instance.user_scores.get(
                user=self.user,
                session=self.session,
            ).score
        except (CompetencyUserScore.DoesNotExist, CompetencyUserScore.MultipleObjectsReturned):
            raise AssessmentSerializerException(
                'Can not find a suitable CompetencyUserScore'
            )


class UserSessionAssessmentScoresSerializer(UserContextSerializerMixin,
                                            serializers.ModelSerializer):
    """Serialize a ``TrainingSession`` with related assessment scores for a user

    Notes
    -----
    This serializer has a mandatory context key ``user``. This is an instance of ``MTPUser``
    whose session assessment scores wil be serialized.

    """

    activity_scores = serializers.SerializerMethodField()
    competency_scores = serializers.SerializerMethodField()
    date_reached = serializers.SerializerMethodField()
    is_forced = serializers.SerializerMethodField()
    is_validated = serializers.SerializerMethodField()
    session_id = serializers.CharField(source='id')
    status = serializers.SerializerMethodField()
    user = serializers.SerializerMethodField()

    class Meta:
        model = TrainingSession
        fields = (
            'activity_scores',
            'competency_scores',
            'date_reached',
            'is_forced',
            'is_validated',
            'session_id',
            'status',
            'user',
        )

    def __init__(self, *args, **kwargs):
        """Check we have a valid context before proceeding and attach certification

        Raises
        ------
        CertificationProgramUserStatus.DoesNotExist:
            When no CertificationProgramUserStatus is found for user + session couple

        """

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

        # we use `.filter.first` here and not `.get`, because of a sync problem
        # that introduced some duplicates
        self.certification = CertificationProgramUserStatus.objects.non_deleted().filter(
            user=self.user,
            session=self.instance,
        ).first()
        if not self.certification:
            raise CertificationProgramUserStatus.DoesNotExist

        self.competency_tree = CompetencyTree.objects.get_for_session(self.instance)

        if not self.competency_tree:
            raise AssessmentSerializerException(
                'Can not find a suitable CompetencyTree'
            )

    def get_activity_scores(self, instance):
        """Return a list of serialized activity scores"""

        return [
            ActivityScoresSerializer(activity, context={
                'user': self.user,
                'session': instance,
            }).data for activity in instance.training.activities.filter(
                is_assessing=True
            ).non_deleted()
        ]

    def get_competency_scores(self, instance):
        """Return a list of serialized competency scores"""

        return [
            CompetencyScoresSerializer(competency, context={
                'user': self.user,
                'session': instance,
            }).data for competency in self.competency_tree.competencies.non_deleted()
        ]

    # pylint: disable=unused-argument
    def get_date_reached(self, instance):
        """Return ``date_reached`` from ``self.certification``"""

        return self.certification.date_reached

    # pylint: disable=unused-argument
    def get_is_forced(self, instance):
        """Return ``is_forced`` from ``self.certification``"""

        return self.certification.is_forced

    # pylint: disable=unused-argument
    def get_is_validated(self, instance):
        """Return ``is_validated`` from ``self.certification``"""

        return self.certification.is_validated

    # pylint: disable=unused-argument
    def get_status(self, instance):
        """Return ``status.name`` from ``self.certification``"""

        return self.certification.status.name if self.certification.status else None

    # pylint: disable=unused-argument
    def get_user(self, instance):
        """Return the serialized user from context"""

        return MTPUserSerializer(self.user).data


class LessonReferenceListSerializer(serializers.ListSerializer):
    """List serializer for the ``LessonReference`` model"""

    def create(self, validated_data):
        """Perform an ``update_or_create`` on each lesson references

        Returns
        -------
        list(LessonReference)
            A list of the created or updated lesson references.

        For the parameters, see ``django_rest.serializers.ListSerializer``

        """

        lesson_references = []

        for validated_item in validated_data:
            lesson_reference, __ = LessonReference.objects.update_or_create(
                external_id=validated_item['external_id'],
                defaults={
                    'external_name': validated_item['external_name'],
                }
            )
            lesson_references.append(lesson_reference)

        return lesson_references


class LessonReferenceSerializer(serializers.ModelSerializer):
    """Serialize a ``LessonReference``"""

    name = serializers.CharField(source='external_name')

    class Meta:
        model = LessonReference
        fields = (
            'name',
            'external_id',
        )
        list_serializer_class = LessonReferenceListSerializer


class RecommendationLevelSerializer(serializers.ModelSerializer):
    """Serialize a ``RecommendationLevel``"""

    class Meta:
        model = RecommendationLevel
        fields = (
            'id',
            'name',
        )


class LessonSerializer(serializers.ModelSerializer):
    """Serialize a ``Lesson``"""

    recommendation_levels = serializers.SerializerMethodField()
    reference = LessonReferenceSerializer()
    topic_name = serializers.SerializerMethodField()

    class Meta:
        model = Lesson
        fields = (
            'name',
            'recommendation_levels',
            'reference',
            'topic_name',
            'type',
        )

    def get_recommendation_levels(self, instance):
        """Return a list of non deleted and serialized recommendation levels"""

        return RecommendationLevelSerializer(
            instance.recommendation_levels.non_deleted(),
            many=True,
        ).data

    def get_topic_name(self, instance):
        """Return the ``name`` from ``lesson.topic``"""

        return instance.topic.name if instance.topic else None


class UserLessonRecommendationSerializer(serializers.ModelSerializer):
    """Serialize a ``UserLessonRecommendation`` object."""

    lesson = LessonSerializer()
    status = serializers.PrimaryKeyRelatedField(source='level', read_only=True)

    class Meta:
        model = UserLessonRecommendation
        fields = (
            'lesson',
            'status',
        )
