"""Test the views of the ``assessments`` app"""

import os
from urllib.parse import urlencode

from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.test import override_settings, RequestFactory

from mtp.apps.accounts.factories import BusinessUnitFactory, GroupRoleFactory
from mtp.apps.accounts.models import Group, GroupRole
from mtp.apps.assessments.factories.certifications import CertificationProgramUserStatusFactory
from mtp.apps.assessments.factories.recommendations import LessonFactory
from mtp.apps.assessments.models import (
    CertificationStatus, CompetencyTree,
    Curriculum, Dimension
)
from mtp.apps.assessments.tests.test_cases import (
    MTPComplexAssessmentTestCase,
    MTPSimpleAssessmentTestCase,
    CertificationResultsTestCase,
)
from mtp.apps.assessments.views import SessionScoresView


class XLSImportViewTestCase(MTPSimpleAssessmentTestCase):
    """ Test case that tests the ``XLSImportView`` class. """

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

        file_path = os.path.join(settings.APPS_ROOT, 'import_export/xls_test_files/assesment.xlsx')
        cls.file = open(file_path, 'rb')

        cls.jobprofile_data = {'name': 'jobprofile_test'}

    def test_creation_assessment_with_import_file(self):
        """ Test the creation of jobprofile and competency tree with import file. """
        url = reverse('assessments:import_xls')
        self.login_as_trainer()

        data = {'file': self.file, 'is_assessing': True}
        self.client.post(url, data=data, follow=True)

        # Test imported object exists
        queryset = Group.objects.filter(**self.jobprofile_data)
        self.assertTrue(queryset.exists())

        # We check imported object contains 1 competency et 3 dimensions.
        tree = CompetencyTree.objects.get(**self.jobprofile_data)
        self.assertEqual(tree.competencies.count(), 1)
        self.assertEqual(
            len(Dimension.objects.filter(competency__tree=tree)),
            3
        )


class BaseAssessmentsViewTestCase(MTPComplexAssessmentTestCase):
    """Base test case to test the assessments-related views"""

    @classmethod
    def setUpTestData(cls):
        """Set up the test data"""

        super().setUpTestData()

        # We use the existing trainer, as a manager
        cls.manager = cls.trainer

        # He is the manager of a fresh business_unit
        cls.business_unit = BusinessUnitFactory()
        GroupRoleFactory(
            user=cls.manager,
            group=cls.business_unit,
            role=GroupRole.ROLES.MANAGER
        )
        # And also a member of the job_profile
        GroupRoleFactory(
            user=cls.manager,
            group=cls.job_profile,
        )

        # Duplicate the competency_tree to this new business unit
        cls.competency_tree.duplicate_to_groups([cls.business_unit])


class CompetencyViewsTestCase(BaseAssessmentsViewTestCase):
    """Test cases for the views related to the competencies models"""

    @classmethod
    def setUpTestData(cls):
        """Set up the test data"""

        super().setUpTestData()

        first_competency = cls.competency_tree.competencies.first()
        first_dimension = first_competency.dimensions.first()

        cls.assessment_urls = [
            cls.competency_tree.get_competency_sort_url(),
            cls.competency_tree.get_competency_create_url(),
            cls.competency_tree.get_duplicate_url(),
            cls.competency_tree.get_curriculum_url(),
            cls.competency_tree.get_certification_program_url(),
            cls.competency_tree.get_certification_status_create_url(),
            cls.competency_tree.get_certification_results_url(),
            first_competency.get_edit_url(),
            first_competency.get_delete_url(),
            first_competency.get_dimension_create_url(),
            first_competency.get_dimension_sort_url(),
            first_dimension.get_edit_url(),
            first_dimension.get_delete_url(),
        ]

    def test_all_views_work(self):
        """Test that the manager of the ``job_profile``can access all the views"""

        self.login_as_admin()

        for url in self.assessment_urls:
            response = self.client.get(url)
            with self.subTest(url=url):
                self.assertEqual(
                    response.status_code,
                    200
                )

    def test_competency_list_view_as_job_profile_manager(self):
        """Test the ``CompetencyListView`` with the manager of the ``job_profile``"""

        self.login_as_admin()

        response = self.client.get(self.competency_tree.get_competency_list_url())

        # The view work
        self.assertEqual(
            response.status_code,
            200
        )

        # We got the correct context data
        self.assertEqual(
            response.context['current_job_profile'],
            self.competency_tree.job_profile
        )
        self.assertCountEqual(
            response.context['job_profiles'],
            self.admin.mgroups.non_deleted().job_profiles().all()
        )
        self.assertCountEqual(
            response.context['managed_groups'],
            self.admin.mgroups.non_deleted().business_units().filter(
                trees__isnull=False
            )
        )
        self.assertEqual(
            response.context['current_managed_group'],
            None
        )

        # The user can edit the competency tree
        self.assertTrue(
            response.context['competency_tree'].can_edit
        )

        # The user cannot translate the competency tree
        self.assertFalse(
            response.context['competency_tree'].can_translate
        )

    def test_competency_list_view_as_business_unit_manager(self):
        """Test the ``CompetencyListView`` with the manager of a ``business_unit``"""

        self.login_as_trainer()

        response = self.client.get(self.competency_tree.get_competency_list_url())

        # The view work
        self.assertEqual(
            response.status_code,
            200
        )

        # We got the correct context data
        self.assertEqual(
            response.context['current_job_profile'],
            self.competency_tree.job_profile
        )
        self.assertCountEqual(
            response.context['job_profiles'],
            self.trainer.mgroups.non_deleted().job_profiles().all()
        )
        self.assertCountEqual(
            response.context['managed_groups'],
            self.trainer.mgroups.non_deleted().business_units().all()
        )
        self.assertEqual(
            response.context['current_managed_group'],
            None
        )

        # The user cannot edit the competency tree
        self.assertFalse(
            response.context['competency_tree'].can_edit
        )

        # The user cannot translate the competency tree
        self.assertFalse(
            response.context['competency_tree'].can_translate
        )

    def test_competency_list_view_as_business_unit_manager_with_group(self):
        """Test the ``CompetencyListView`` with the manager of a ``business_unit``

        In this test we access the duplicated instance of the ``CompetencyTree``

        """

        self.login_as_trainer()

        duplicated_competency_list_url = '%s?group_uuid=%s' % (
            reverse('assessments:competency-list'),
            self.business_unit.pk
        )

        response = self.client.get(duplicated_competency_list_url)

        # The view work
        self.assertEqual(
            response.status_code,
            200
        )

        # The user cannot edit the competency tree
        self.assertFalse(
            response.context['competency_tree'].can_edit
        )

        # The user may translate the competency tree
        self.assertTrue(
            response.context['competency_tree'].can_translate
        )


class RecommendationsViewsTestCase(BaseAssessmentsViewTestCase):
    """Test cases for the views related to the recommendations models"""

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.first_topic = cls.curriculum.topics.first()

    def get_recommendations_urls(self, curriculum):
        """Shortcut method to get the recommendations url for the given curriculum

        Parameters
        ----------
        curriculum: ``mtp.apps.assessments.models.Curriculum`` instance
            The curriculum we want the urls for

        Returns
        -------
        list
            A list of url

        """

        first_lesson = self.curriculum.lessons.first()

        lesson_dimension_weight = first_lesson.dimension_weights.first()
        lesson_recommendation_threshold = first_lesson.recommendation_thresholds.first()

        return [
            curriculum.get_absolute_url(),
            curriculum.get_topic_create_url(),
            self.first_topic.get_edit_url(),
            self.first_topic.get_delete_url(),
            self.first_topic.get_lesson_create_url(),
            first_lesson.get_edit_url(),
            first_lesson.get_delete_url(),
            first_lesson.get_dimension_weight_create_url(),
            first_lesson.get_recommendation_level_create_url(),
            lesson_recommendation_threshold.get_edit_url(),
            lesson_recommendation_threshold.get_delete_url(),
            first_lesson.get_dimension_weight_create_url(),
            lesson_dimension_weight.get_edit_url(),
            lesson_dimension_weight.get_delete_url(),
        ]

    def test_all_views_work_as_job_profile_manager(self):
        """Test that the manager of the ``job_profile``can access all the views"""

        self.login_as_admin()

        for url in self.get_recommendations_urls(self.curriculum):
            with self.subTest(url=url):
                response = self.client.get(url)
                self.assertEqual(
                    response.status_code,
                    200
                )

    @override_settings(LANGUAGE_CODE='en')
    def test_curriculum_view_as_business_unit_manager(self):
        """Test the curriculum view logged in as a business unit manager"""

        self.login_as_trainer()

        # The business unit have no curriculum
        with self.assertRaises(ObjectDoesNotExist):
            Curriculum.objects.get(
                job_profile=self.job_profile,
                business_unit=self.business_unit
            )

        curriculum_url = '%s?group_uuid=%s' % (
            self.curriculum.get_absolute_url(),
            self.business_unit.pk
        )

        # Get the url of the specific curriculum for this business unit
        response = self.client.get(curriculum_url)

        self.assertEqual(response.status_code, 200)  # The route work

        # There is a curriculum in the context, as it was created "on-the-fly"
        self.assertEqual(
            response.context['curriculum'],
            Curriculum.objects.get(
                job_profile=self.job_profile,
                business_unit=self.business_unit
            )
        )

    def test_topic_soft_delete_view(self):
        """Test the topic soft-delete view."""

        # We create 3 lessons for the first non-deleted topic.
        self.assertFalse(self.first_topic.is_deleted)
        LessonFactory.create_batch(3, topic=self.first_topic)

        # We soft-delete the first topic.
        self.login_as_admin()
        self.client.post(self.first_topic.get_delete_url())

        # We check that the first topic is soft-deleted.
        self.first_topic.refresh_from_db()
        self.assertTrue(self.first_topic.is_deleted)

        # We check if we have 3 soft-deleted lessons in the soft-deleted topic.
        lessons = self.first_topic.lessons.all()
        self.assertEqual(lessons.count(), 3)
        for lesson in lessons:
            with self.subTest(lesson=lesson):
                self.assertTrue(lesson.is_deleted)


class CertificationResultsViewsTestCase(BaseAssessmentsViewTestCase, CertificationResultsTestCase):
    """Test case for the views related to the ``CertificationResults`` model"""

    def test_results_list_view_as_job_profile_manager(self):
        """Test the results list view logged-in as the job_profile manager"""

        self.login_as_admin()

        response = self.client.get(
            self.competency_tree.get_certification_results_url()
        )

        # The route work
        self.assertEqual(response.status_code, 200)

        # The context is correct
        self.assertCountEqual(
            response.context['available_certification_statuses'],
            CertificationStatus.objects.non_deleted().filter(
                program__tree=self.competency_tree
            )
        )

        self.assertCountEqual(
            response.context['certificationprogramuserstatus_list'],
            self.user_certifications
        )

        self.assertTrue(response.context['is_job_profile_manager'])

    def test_results_list_view_as_business_unit_manager(self):
        """Test the results list view logged-in as the business unit manager"""

        self.login_as_trainer()

        response = self.client.get(
            self.competency_tree.get_certification_results_url()
        )

        # The route work
        self.assertEqual(response.status_code, 200)

        # The context is correct
        self.assertCountEqual(
            response.context['available_certification_statuses'],
            CertificationStatus.objects.non_deleted().filter(
                program__tree=self.competency_tree
            )
        )

        # The user cannot see any of the results,
        # since he is not the manager of the business unit the results come from
        self.assertCountEqual(
            response.context['certificationprogramuserstatus_list'],
            []
        )

        self.assertFalse(response.context['is_job_profile_manager'])

    def test_results_detail_view_as_job_profile_manager(self):
        """Test the results detail view logged-in as the job_profile manager"""

        self.login_as_admin()

        response = self.client.get(
            self.user_certifications[0].get_detail_url()
        )

        # The user have access
        self.assertEqual(response.status_code, 200)

    def test_results_detail_view_as_business_unit_manager(self):
        """Test the results detail view logged-in as the business_unit manager"""

        self.login_as_trainer()

        response = self.client.get(
            self.user_certifications[0].get_detail_url()
        )

        # The user don't have access
        self.assertEqual(response.status_code, 404)

    def test_results_force_status_view_as_job_profile_manager(self):
        """Test the view to force a status on a result as the job_profile manager"""

        self.login_as_admin()

        self.assertEqual(
            self.user_certifications[0].status,
            self.certification_statuses[0]
        )

        response = self.client.post(
            self.user_certifications[0].get_update_url(),
            {
                'status': self.certification_statuses[1].pk
            }
        )

        self.assertEqual(response.status_code, 302)

        # The status should be updated
        self.user_certifications[0].refresh_from_db()
        self.assertEqual(
            self.user_certifications[0].status,
            self.certification_statuses[1]
        )
        self.assertTrue(self.user_certifications[0].is_forced)

    def test_results_force_status_view_as_business_unit_manager(self):
        """Test the view to force a status on a result as the business_unit manager"""

        self.login_as_trainer()

        response = self.client.post(
            self.user_certifications[0].get_update_url(),
            {
                'status': self.certification_statuses[1].pk
            }
        )

        # The business_unit manager can't force a status
        self.assertEqual(response.status_code, 404)

    def test_validate_view(self):
        """Test the view to validate a status"""

        self.login_as_admin()

        self.assertFalse(self.user_certifications[0].is_validated)

        response = self.client.post(
            self.user_certifications[0].get_validate_url()
        )

        self.assertEqual(response.status_code, 200)

        # The certification should be marked as validated
        self.user_certifications[0].refresh_from_db()

        self.assertEqual(
            self.user_certifications[0].status,
            self.certification_statuses[0]
        )

        self.assertTrue(self.user_certifications[0].is_validated)


class CertificationResultsListFilterTestCase(BaseAssessmentsViewTestCase):
    """Test case for the filters on the ``CertificationResultsListView``"""

    @classmethod
    def setUpTestData(cls):
        """Set up the test data"""

        super().setUpTestData()

        # One "validated" certification result
        cls.validated_status_1 = CertificationProgramUserStatusFactory(
            program=cls.certification_program,
            status=cls.certification_statuses[0],
            session=cls.training_session,
            is_validated=True
        )

        # One with a different status
        cls.validated_status_2 = CertificationProgramUserStatusFactory(
            program=cls.certification_program,
            status=cls.certification_statuses[1],
            session=cls.training_session,
            is_validated=True,
        )

        # One "non-validated" certification result
        cls.non_validated_status = CertificationProgramUserStatusFactory(
            program=cls.certification_program,
            status=cls.certification_statuses[0],
            session=cls.training_session,
            is_validated=False
        )

        cls.validated_statuses = [
            cls.validated_status_1,
            cls.validated_status_2
        ]

        cls.non_validated_statuses = [
            cls.non_validated_status
        ]

        cls.statuses_with_status_0 = [
            cls.validated_status_1,
            cls.non_validated_status
        ]

        cls.statuses_with_status_1 = [
            cls.validated_status_2
        ]

        cls.all_statuses = [
            cls.validated_status_1,
            cls.validated_status_2,
            cls.non_validated_status
        ]

    def get_statuses_from_view(self, filters):
        """Call the results list view and get the results in the context

        Parameters
        ----------
        filters: dict

        Returns
        -------
        list
            The list of the ``CertificationProgramUserStatus`` in the context

        """

        self.login_as_admin()

        url = '%s?%s' % (
            self.competency_tree.get_certification_results_url(),
            urlencode(filters)
        )

        response = self.client.get(url)

        return response.context['certificationprogramuserstatus_list']

    def test_results_list_view_filtering_adressed(self):
        """Test the filtering of the results list with the ``is_validated`` filter

        Values for the the is_validated filter are:
        - 1 : All
        - 2 : Validated
        - 3 : Non-validated

        """

        self.assertCountEqual(
            self.get_statuses_from_view({
                'is_validated': '1'
            }),
            self.validated_statuses + self.non_validated_statuses
        )

        self.assertCountEqual(
            self.get_statuses_from_view({
                'is_validated': '2'
            }),
            self.validated_statuses
        )

        self.assertCountEqual(
            self.get_statuses_from_view({
                'is_validated': '3'
            }),
            self.non_validated_statuses
        )

    def test_results_list_view_filtering_status(self):
        """Test the filtering of the results list with the ``status`` filter"""

        self.assertCountEqual(
            self.get_statuses_from_view({
                'status': self.certification_statuses[0].pk
            }),
            self.statuses_with_status_0
        )

        self.assertCountEqual(
            self.get_statuses_from_view({
                'status': self.certification_statuses[1].pk
            }),
            self.statuses_with_status_1
        )

    def test_results_list_view_filtering_trainee_first_name(self):
        """Test the filtering of the results list with the ``user__last_name`` filter"""

        self.assertCountEqual(
            self.get_statuses_from_view({
                'user__last_name': 'Nee'
            }),
            self.all_statuses
        )

        self.assertCountEqual(
            self.get_statuses_from_view({
                'user__last_name': 'Lastnamenotused'
            }),
            []
        )


class CertificationResultsPDFViewTestCase(BaseAssessmentsViewTestCase,
                                          CertificationResultsTestCase):
    """Test the ``CertificationResultsPDFView`` view."""

    def test_view_works(self):
        """Test that the pdf detail view works."""

        self.login_as_admin()

        response = self.client.get(
            self.user_certifications[0].get_pdf_url()
        )
        self.assertEqual(response.status_code, 200)


class CertificationStatusViewsTestCase(BaseAssessmentsViewTestCase):
    """Test case for the views related to the ``CertificationStatus`` model."""

    def test_reference_view_works(self):
        """Test that the view to change reference works."""

        # By default, a certification status is not flagged as reference.
        certification_status = self.certification_statuses[0]
        self.assertFalse(certification_status.is_reference)

        # We flag this certification status thanks to reference view.
        self.login_as_admin()
        response = self.client.get(certification_status.get_reference_url())
        self.assertEqual(response.status_code, 302)

        # Now, this certification status must be flagged as reference.
        certification_status.refresh_from_db()
        self.assertTrue(certification_status.is_reference)


class CompetencyTreeImportTranslationTestCase(MTPComplexAssessmentTestCase):
    """Test the ``CompetencyTreeImportTranslationView`` works."""

    def test_view_works(self):
        """Test the view works."""

        job_profile = self.competency_tree.job_profile

        url = reverse('assessments:import-translation', kwargs={
            'jobprofile_uuid': job_profile.id,
        })

        self.login_as_trainer()
        response = self.client.get(url)

        self.assertEqual(response.status_code, 404)

        self.login_as_admin()
        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)


class SessionScoresViewTestCase(MTPComplexAssessmentTestCase):
    """Test case for the ``SessionScoresView`` view."""

    @classmethod
    def setUpTestData(cls):
        """We create assessments scores."""

        super().setUpTestData()

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

    def get_view(self, *args, **kwargs):
        """Return the ``SessionScoresView`` view to test."""

        url_with_filters = self.get_url_with_filters('/fake/path', {'training': self.training.pk})
        request = RequestFactory().get(url_with_filters)
        request.user = self.trainer

        view = SessionScoresView()
        view.request = request
        view.args = args
        view.kwargs = kwargs
        view.get(request)

        return view

    @staticmethod
    def get_url_with_filters(url, filters):
        """Get the url with filters.

        Parameters
        ----------
        filters: dict

        Returns
        -------
        str
            The wanted url.

        """

        return '%s?%s' % (url, urlencode(filters))

    def test_get_session_list(self):
        """Test the return value of ``session_list`` property."""

        view = self.get_view()

        self.assertEqual(
            list(view.session_list),
            [self.training_session]
        )

    def test_get_selected_training(self):
        """Test the return value of ``selected_training`` property."""

        view = self.get_view()

        self.assertEqual(view.selected_training, self.training)

    def test_get_filename(self):
        """Test the return value of ``get_filename`` method."""

        view = self.get_view()

        self.assertEqual(view.get_filename(), 'scores-%s' % self.training)

    def test_view_works(self):
        """Check if the view return a xlsx data."""

        filters = {
            'training': self.training.pk
        }

        self.login_as_trainer()
        url = reverse('assessments:export_sessions_scores')
        url_with_filters = self.get_url_with_filters(url, filters)

        self.training_session.close()

        response = self.client.get(url_with_filters)

        self.assertEqual(response.status_code, 200)
        self.assertIsNotNone(response.content)
