"""Test the exporters of the ``assessments`` app."""

import os
import shutil
from unittest.mock import patch

from django.conf import settings
from django.utils import formats

from ..exporters import (
    ActivitiesScoresExporter,
    AssessmentScoresExporter,
    CertificationExporter,
    CompetenciesScoresExporter,
    RecommendationsExporter,
)
from ..models import CompetencyTree
from .test_cases import MTPComplexAssessmentTestCase


class BaseExporterTestCase(MTPComplexAssessmentTestCase):
    """Base test case to test assessments exporters."""

    @classmethod
    def setUpClass(cls):
        """We change users in training session."""

        super().setUpClass()

        # We use only ``cls.trainee`` in training session
        # because our ``_record_*`` methods works only with him.
        cls.training_session.users.clear()
        cls.training_session.users.add(cls.trainee)

        cls.sessions = [cls.training_session]

    @classmethod
    def setUpTestData(cls):
        """We create several scores to prepare extraction."""

        super().setUpTestData()

        cls._record_dimension_scores([69, 65, 80, 98, 91, 77])
        cls._record_competency_scores([70, 89])
        cls._record_global_score(75)
        cls._compute_certification_status()
        cls._compute_recommendations()


class CompetenciesExporterTestCase(BaseExporterTestCase):
    """Test the ``CompetenciesExporter`` class."""

    @classmethod
    def setUpClass(cls):
        """Create the competency scores exporter."""

        super().setUpClass()

        cls.exporter = CompetenciesScoresExporter(cls.sessions, cls.competency_tree)

    def test_get_competencies_scores(self):
        """Test the return value of the ``_get_competencies_scores`` method."""

        self.assertListEqual(
            list(self.exporter._get_competencies_scores(self.sessions)),
            [
                {'competency_id': self.competencies[0].id, 'user_id': self.trainee.id,
                 'session_id': self.training_session.id, 'score': 70},
                {'competency_id': self.competencies[1].id, 'user_id': self.trainee.id,
                 'session_id': self.training_session.id, 'score': 89},
            ]
        )

    def test_get_dimensions_scores(self):
        """Test the return value of the ``_get_dimensions_scores`` method."""

        self.assertListEqual(
            list(self.exporter._get_dimensions_scores(self.sessions)),
            [
                {'score': 98, 'dimension__competency_id': self.competencies[1].id,
                 'dimension_id': self.dimensions[3].id, 'session_id': self.training_session.id,
                 'user_id': self.trainee.id},
                {'score': 69, 'dimension__competency_id': self.competencies[0].id,
                 'dimension_id': self.dimensions[0].id, 'session_id': self.training_session.id,
                 'user_id': self.trainee.id},
                {'score': 91, 'dimension__competency_id': self.competencies[1].id,
                 'dimension_id': self.dimensions[4].id, 'session_id': self.training_session.id,
                 'user_id': self.trainee.id},
                {'score': 65, 'dimension__competency_id': self.competencies[0].id,
                 'dimension_id': self.dimensions[1].id, 'session_id': self.training_session.id,
                 'user_id': self.trainee.id},
                {'score': 77, 'dimension__competency_id': self.competencies[1].id,
                 'dimension_id': self.dimensions[5].id, 'session_id': self.training_session.id,
                 'user_id': self.trainee.id},
                {'score': 80, 'dimension__competency_id': self.competencies[0].id,
                 'dimension_id': self.dimensions[2].id, 'session_id': self.training_session.id,
                 'user_id': self.trainee.id},
            ]
        )

    def test_get_competencies_names(self):
        """Test the return value of the ``get_competencies_names`` method."""

        self.assertListEqual(
            self.exporter.get_competencies_names(),
            [
                self.competencies[0].name,
                self.dimensions[0].name, self.dimensions[1].name, self.dimensions[2].name,
                self.competencies[1].name,
                self.dimensions[3].name, self.dimensions[4].name, self.dimensions[5].name,
            ]
        )

    def test_get_competencies_scores_for_trainee(self):
        """Test the return value of the ``get_competencies_scores_for_trainee`` method."""

        self.assertListEqual(
            self.exporter.get_competencies_scores_for_trainee(
                self.training_session.id, self.trainee.id
            ),
            [70, 69, 65, 80, 89, 98, 91, 77]
        )


class ActivitiesExporterTestCase(BaseExporterTestCase):
    """Test the ``ActivitiesExporter`` class."""

    @classmethod
    def setUpClass(cls):
        """Create the activities scores exporter."""

        super().setUpClass()

        cls.exporter = ActivitiesScoresExporter(cls.sessions)

    def test_get_original_sessions_activities(self):
        """Test the return value of the ``_get_original_sessions_activities`` method."""

        self.assertListEqual(
            self.exporter._get_original_sessions_activities(self.sessions),
            [
                {'order': 10, 'name': self.quiz.name, 'id': self.quiz.id},
                {'order': 20, 'name': self.roleplay.name, 'id': self.roleplay.id},
            ]
        )

    def test_get_activities_scores(self):
        """Test the return value of the ``_get_activities_scores`` method."""

        # In this example, we don't have ``AssessmentPartScore``, so no activities scores.
        self.assertEqual(
            self.exporter._get_activities_scores(self.sessions),
            []
        )

    def test_get_activities_names(self):
        """Test the return value of the ``get_activities_names`` method."""

        self.assertListEqual(
            self.exporter.get_activities_names(),
            [
                self.quiz.name,
                self.roleplay.name,
            ]
        )

    def test_get_activities_scores_for_trainee(self):
        """Test the return value of the ``get_activities_scores_for_trainee`` method."""

        self.assertEqual(
            self.exporter.get_activities_scores_for_trainee(
                self.training_session.id, self.trainee.id
            ),
            [0, 0]
        )


class CertificationExporterTestCase(BaseExporterTestCase):
    """Test the ``CertificationExporter`` class."""

    @classmethod
    def setUpClass(cls):
        """Create the certification exporter."""

        super().setUpClass()

        cls.exporter = CertificationExporter(cls.sessions)

    def test_get_certifications(self):
        """Test the return value of the ``_get_certifications`` method."""

        self.assertListEqual(
            list(self.exporter._get_certifications(self.sessions)),
            [
                {'user_id': self.trainee.id,
                 'status__name': 'Level 2',
                 'session_id': self.training_session.id},
            ]
        )

    def test_get_global_scores(self):
        """Test the return value of the ``_get_global_scores`` method."""

        self.assertListEqual(
            list(self.exporter._get_global_scores(self.sessions)),
            [
                {'user_id': self.trainee.id,
                 'score': 75,
                 'session_id': self.training_session.id},
            ]
        )

    def test_get_global_score_for_trainee(self):
        """Test the return value of the ``get_global_score_for_trainee`` method."""

        self.assertEqual(
            self.exporter.get_global_score_for_trainee(
                self.training_session.id, self.trainee.id
            ),
            75
        )

    def test_get_certification_for_trainee(self):
        """Test the return value of the ``get_certification_for_trainee`` method."""

        self.assertEqual(
            self.exporter.get_certification_for_trainee(
                self.training_session.id, self.trainee.id
            ),
            'Level 2'
        )


class RecommendationsExporterTestCase(BaseExporterTestCase):
    """Test the ``RecommendationsExporter`` class."""

    @classmethod
    def setUpClass(cls):
        """Create the recommendations exporter."""

        super().setUpClass()

        cls.exporter = RecommendationsExporter(cls.sessions, cls.curriculum)

    def test_get_lessons(self):
        """Test the return value of the ``_get_lessons`` method."""

        self.assertListEqual(
            list(self.exporter._get_lessons(self.curriculum)),
            [
                {'name': "Best practices for today's developers", 'id': self.lessons[1].id},
                {'name': 'Understanding diversity in a development team', 'id': self.lessons[2].id},
                {'name': 'Web development 101', 'id': self.lessons[0].id},
            ]
        )

    def test_get_recommendations(self):
        """Test the return value of the ``_get_recommendations`` method."""

        self.assertListEqual(
            list(self.exporter._get_recommendations(self.sessions)),
            [
                {'certification__user_id': self.trainee.id,
                 'lesson__name': "Best practices for today's developers",
                 'level__name': 'Recommended',
                 'certification__session_id': self.training_session.id},
                {'certification__user_id': self.trainee.id,
                 'lesson__name': 'Understanding diversity in a development team',
                 'level__name': 'Optional',
                 'certification__session_id': self.training_session.id},
                {'certification__user_id': self.trainee.id,
                 'lesson__name': 'Web development 101',
                 'level__name': 'Recommended',
                 'certification__session_id': self.training_session.id},
            ]
        )

    def test_get_lessons_names(self):
        """Test the return value of the ``get_lessons_names`` method."""

        self.assertListEqual(
            self.exporter.get_lessons_names(),
            [
                "Best practices for today's developers",
                "Understanding diversity in a development team",
                "Web development 101",
            ]
        )

    def test_get_lessons_levels_for_trainee(self):
        """Test the return value of the ``get_lessons_levels_for_trainee`` method."""

        self.assertEqual(
            self.exporter.get_lessons_levels_for_trainee(
                self.training_session.id, self.trainee.id
            ),
            ['Recommended', 'Optional', 'Recommended']
        )


class AssessmentScoresExporterTestCase(BaseExporterTestCase):
    """Test the ``AssessmentScoresExporter`` class."""

    @classmethod
    def setUpClass(cls):
        """Create the assessment scores exporter."""

        super().setUpClass()

        cls.exporter = AssessmentScoresExporter(cls.sessions)

        cls.filename = 'file.xlsx'
        cls.path = os.path.join(settings.PROJECT_ROOT, 'fake/path')
        cls.file_path = os.path.join(cls.path, cls.filename)

        # We create path to save the file
        if not os.path.exists(cls.path):
            os.makedirs(cls.path)

    def test_build_sessions_scores(self):
        """Test the return value of the ``_build_sessions_scores`` method."""

        # The competency tree score is already computed in ``setUpTestData`` method.
        # We don't want force recomputed it because we don't have ``AssessmentPartScore``
        with patch.object(CompetencyTree, 'compute_scores', return_value=None):
            data = self.exporter._build_sessions_scores(
                self.sessions, self.competency_tree, self.curriculum
            )

        excepted_data = [
            ['Business unit',
             'Session date',
             'Assessor', 'Username', 'Full name',
             'Global score', 'Certification',
             self.quiz.name, self.roleplay.name,
             'Development', 'Code', 'Tests', 'Documentation',
             'Teamwork', 'Respect', 'Reliability', 'Autonomy',
             "Best practices for today's developers",
             'Understanding diversity in a development team',
             'Web development 101'],
            [self.training.business_unit.name,
             formats.date_format(self.training_session.start_date, "SHORT_DATE_FORMAT"),
             self.trainer.get_full_name(), self.trainee.username, self.trainee.get_full_name(),
             75, 'Level 2',
             0, 0,
             70, 69, 65, 80,
             89, 98, 91, 77,
             'Recommended',
             'Optional',
             'Recommended']
        ]

        self.assertEqual(data, excepted_data)

    def test_get_header_row(self):
        """Test the return value of the ``_get_header_row`` method."""

        with patch.object(CompetencyTree, 'compute_scores', return_value=None):
            header_row = self.exporter._get_header_row()

        excepted_header_row = [
            'Business unit', 'Session date', 'Assessor', 'Username', 'Full name',
            'Global score', 'Certification',
            self.quiz.name, self.roleplay.name,
            'Development', 'Code', 'Tests', 'Documentation',
            'Teamwork', 'Respect', 'Reliability', 'Autonomy',
            "Best practices for today's developers",
            'Understanding diversity in a development team',
            'Web development 101'
        ]

        self.assertEqual(header_row, excepted_header_row)

    def test_get_file_content(self):
        """Test the return value of the ``get_file_content`` method."""

        with patch.object(CompetencyTree, 'compute_scores', return_value=None):
            file_content = self.exporter.get_file_content()

        self.assertIsNotNone(file_content)

    def test_generate_file(self):
        """Test the return value of the ``generate_file`` method."""

        self.exporter.generate_file(self.file_path)
        self.assertTrue(os.path.exists(self.file_path))
        self.assertTrue(os.path.isfile(self.file_path))
        self.assertNotEqual(os.stat(self.file_path).st_size, 0)

    @classmethod
    def tearDownClass(cls):
        """Remove file path and its content."""

        super().tearDownClass()

        # We remove file and its path
        shutil.rmtree(
            os.path.dirname(cls.path),
            ignore_errors=True
        )
