"""This modules holds recommendations tests for the ``assessments`` app."""

from django.core.exceptions import ValidationError

from mtp.apps.assessments.factories import (
    CertificationProgramUserStatusFactory,
    CertificationStatusFactory,
    LessonFactory,
    LessonRecommendationThresholdFactory,
    RecommendationLevelFactory,
)
from mtp.apps.common.tests.test_cases import MTPTestCase

from ..factories.recommendations import CurriculumFactory
from ..models import Curriculum, RecommendationLevel

from .test_cases import MTPComplexAssessmentTestCase, MTPSimpleAssessmentTestCase


class RecommendationEngineTestCase(MTPComplexAssessmentTestCase):
    """Test case that tests the recommendation computations."""

    @classmethod
    def setUpTestData(cls):
        """We create user certification to allow the computation of recommendations."""

        super().setUpTestData()

        cls.status = CertificationStatusFactory(
            program=cls.certification_program,
        )
        CertificationProgramUserStatusFactory(
            status=cls.status,
            session=cls.training_session,
            user=cls.trainee,
        )

    def test_recommendation_computations(self):
        """Test that the recommendation computations work properly."""

        # pylint: disable=invalid-name
        NO_REC = []
        MANDATORY, RECOMMENDED, OPTIONAL = [name for name in self.LEVEL_NAMES]

        # Without any dimension score, no recommendations.
        self.assertEqual(self._compute_recommendation_levels(), NO_REC)

        # With abysmal dimension score, no recommendations.
        self._record_dimension_scores([0, 0, 0, 0, 0, 0])
        self.assertEqual(self._compute_recommendation_levels(), NO_REC)

        # With the same dimension scores than in the integration test,
        # we should get the same recommendations.
        self._record_dimension_scores([18, 72, 100, 48, 77, 100])
        self.assertEqual(self._compute_recommendation_levels(), [
            MANDATORY, RECOMMENDED, RECOMMENDED
        ])

        # Let's try several possibilities now...
        self._record_dimension_scores([30, 30, 30, 30, 30, 30])
        self.assertEqual(self._compute_recommendation_levels(), [
            MANDATORY, MANDATORY, MANDATORY
        ])

        self._record_dimension_scores([60, 60, 60, 60, 60, 60])
        self.assertEqual(self._compute_recommendation_levels(), [
            RECOMMENDED, RECOMMENDED, RECOMMENDED
        ])

        self._record_dimension_scores([90, 90, 90, 90, 90, 90])
        self.assertEqual(self._compute_recommendation_levels(), [
            OPTIONAL, OPTIONAL, OPTIONAL
        ])

        self._record_dimension_scores([90, 30, 30, 30, 30, 30])
        self.assertEqual(self._compute_recommendation_levels(), [
            RECOMMENDED, MANDATORY, MANDATORY
        ])

        self._record_dimension_scores([90, 60, 60, 30, 30, 30])
        self.assertEqual(self._compute_recommendation_levels(), [
            OPTIONAL, RECOMMENDED, MANDATORY
        ])

        self._record_dimension_scores([30, 90, 90, 30, 30, 30])
        self.assertEqual(self._compute_recommendation_levels(), [
            RECOMMENDED, OPTIONAL, MANDATORY
        ])

        self._record_dimension_scores([90, 90, 90, 30, 30, 30])
        self.assertEqual(self._compute_recommendation_levels(), [
            OPTIONAL, OPTIONAL, MANDATORY
        ])

        self._record_dimension_scores([90, 90, 90, 60, 30, 30])
        self.assertEqual(self._compute_recommendation_levels(), [
            OPTIONAL, OPTIONAL, RECOMMENDED
        ])

        self._record_dimension_scores([90, 90, 90, 90, 60, 60])
        self.assertEqual(self._compute_recommendation_levels(), [
            OPTIONAL, OPTIONAL, OPTIONAL
        ])


class CurriculumQuerySetTestCase(MTPSimpleAssessmentTestCase):
    """Test case for the methods of the ``CurriculumQuerySet``"""

    def test_get_for_session(self):
        """Test the ``get_for_session`` method"""

        # Create one ``Curriculum`` with a ``job_profile`` but no ``business_unit``
        curriculum = CurriculumFactory(job_profile=self.job_profile)

        # The ``get_for_session`` method should return the curriculum
        self.assertEqual(
            Curriculum.objects.get_for_session(self.training_session),
            curriculum
        )

        # Now, we create another ``Curriculum`` with the ``business_unit`` set
        curriculum_for_business_unit = CurriculumFactory(
            job_profile=self.job_profile,
            business_unit=self.business_unit
        )

        # The ``get_for_session`` method should now return the new curriculum
        self.assertEqual(
            Curriculum.objects.get_for_session(self.training_session),
            curriculum_for_business_unit
        )


class RecommendationLevelReferenceTestCase(MTPComplexAssessmentTestCase):
    """Test case that tests the recommendation level reference."""

    @classmethod
    def setUpTestData(cls):
        """Database initialization."""
        super().setUpTestData()

        # We flag a recommendation level as reference
        cls.recommendation_level = cls.levels[1]
        cls.recommendation_level.flag_as_reference()

    def setUp(self):
        """Refresh ``recommendation_level`` from database."""
        super().setUp()

        self.recommendation_level.refresh_from_db()

    def test_reference_manager(self):
        """Test reference manager."""

        references = RecommendationLevel.references
        self.assertEqual(references.count(), 1)
        self.assertEqual(references.get(), self.recommendation_level)

    def test_get_reference_recommendation_threshold_from_lesson(self):
        """Test `get_reference_recommendation_threshold` method from a lesson."""

        lesson = self.lessons[0]
        self.assertEqual(lesson.reference_recommendation_threshold.threshold, 50)

        self.recommendation_level.unflag_as_reference()

        # We check that there is no reference recommendation level
        self.assertEqual(RecommendationLevel.references.count(), 0)

        self.assertIsNone(lesson.reference_recommendation_threshold)

    def test_the_reference_uniqueness(self):
        """Test the uniqueness during reference level marking."""

        references_count = RecommendationLevel.references.count()
        self.assertEqual(references_count, 1)
        self.assertTrue(self.recommendation_level.is_reference)

        # We mark a other level as reference
        other_recommendation_level = self.levels[0]
        other_recommendation_level.flag_as_reference()

        references_count = RecommendationLevel.references.count()
        self.assertEqual(references_count, 1)
        self.assertTrue(other_recommendation_level.is_reference)

    def test_validate_reference_uniqueness(self):
        """Test the ``validate_reference_uniqueness`` method."""

        # We mark manually another level as reference
        other_recommendation_level = self.levels[0]
        other_recommendation_level.is_reference = True

        # Impossible to set two references levels
        with self.assertRaises(ValidationError):
            other_recommendation_level.validate_reference_uniqueness()

        other_recommendation_level.unflag_all_instances()

        # Now, its works !
        other_recommendation_level.validate_reference_uniqueness()


class LessonModelTestCase(MTPTestCase):
    """Test case for the ``Lesson`` model"""

    def test_can_add_recommendation_level_threshold(self):
        """Test the ``can_add_recommendation_level_threshold`` property"""

        # Create a lesson, and two recommendation levels
        lesson = LessonFactory()

        level_1 = RecommendationLevelFactory()
        level_2 = RecommendationLevelFactory()

        # Since the recommendation levels are not linked yet...
        self.assertTrue(lesson.can_add_recommendation_level_threshold)

        # Link one level
        LessonRecommendationThresholdFactory(
            lesson=lesson,
            level=level_1
        )

        # We can still add a recommendation level
        del lesson.can_add_recommendation_level_threshold
        self.assertTrue(lesson.can_add_recommendation_level_threshold)

        # Link the other level
        LessonRecommendationThresholdFactory(
            lesson=lesson,
            level=level_2
        )

        # We can't add another level (both are taken)
        del lesson.can_add_recommendation_level_threshold
        self.assertFalse(lesson.can_add_recommendation_level_threshold)
