"""This module contains the base test cases for the SABA module"""

import copy
import requests
import requests_mock

from django.test import TestCase

from django_lockable.models import Lockable
from django_lockable.utils import _no_fields_locking
from pinax.notifications.models import NoticeType

from mtp.apps.assessments.factories.certifications import CertificationProgramUserStatusFactory
from mtp.apps.assessments.factories.recommendations import (
    UserLessonRecommendationFactory,
    RecommendationLevelFactory,
)
from mtp.apps.assessments.models import CertificationProgramUserStatus, LessonReference
from mtp.apps.assessments.tests.test_cases import MTPComplexAssessmentTestCase

from ..client import SabaApiClient, SabaApiException
from ..tasks import AssessmentsResultsToSabaTask


class BaseSabaApiClientTestCase(TestCase):
    """Base test case for the SABA api client"""

    SABA_API_URL = 'mock://saba_mocked_api'
    SABA_USERNAME = 'testusername'
    SABA_PASSWORD = 'testpassword'

    SABA_LOGIN_URL = SABA_API_URL + '/v1/login'
    SABA_USER_DETAILS_URL = SABA_API_URL + '/v1/people/username=%s:(%s)'
    SABA_ENROLLMENTS_SEARCH_URL = SABA_API_URL + '/v1/learning/enroll/search'
    SABA_ENROLLMENTS_DETAILS_URL = SABA_API_URL + '/v1/enrollments/%s/sections:regdetail'
    SABA_ATTENDANCE_URL = SABA_API_URL + '/v1/attendance'
    SABA_ENROLLMENTS_STATUS = SABA_API_URL + '/v1/learning/enroll/%s/status'
    SABA_TRANSCRIPT_URL = SABA_API_URL + '/v1/transcript/'
    SABA_TRANSCRIPT_DELETE_URL = SABA_API_URL + '/v1/transcripts/%s'
    SABA_TRANSCRIPT_SEARCH_URL = SABA_API_URL + '/v1/people/%s/transcripts/search'
    SABA_COURSE_ACTIVITY_URL = '/v1/courseactivity/'
    SABA_COURSE_ACTIVITY_FILTER_URL = '/v1/courseactivity'
    SABA_COURSE_DETAILS_URL = SABA_API_URL + '/v1/course/?q=(course_no==%s)'
    SABA_COURSE_BY_INTERNAL_ID_URL = SABA_API_URL + '/v1/course/%s'

    @classmethod
    def setUpClass(cls):
        """Set up the requests mock adapter"""

        super().setUpClass()

        session = requests.Session()
        cls.adapter = requests_mock.Adapter()

        with requests_mock.Mocker() as mock:
            mock.register_uri(
                'GET',
                cls.SABA_LOGIN_URL,
                json={
                    'certificate': 'mockedcertificate',
                    '@type': 'SabaCertificate'
                }
            )
            cls.mocked_saba_api_client = SabaApiClient(
                cls.SABA_API_URL,
                cls.SABA_USERNAME,
                cls.SABA_PASSWORD
            )

        session.mount('mock', cls.adapter)
        cls.mocked_saba_api_client._session = session


class MockedDjangoTask():
    """Mock ``DjangoTask`` for our tests"""

    class MockedRedisInstance():
        """Mock ``redis_instance`` for our tests"""

        results = {}

    redis_instance = MockedRedisInstance()


class MockedSabaApiClient():
    """A mocked SabaApiClient for our tests"""

    SABA_USER_DETAILS = {
        'id': 'SABA_TRAINEE_ID',
        'details': 'XXX',
    }
    SABA_COURSE_DETAILS = {
        'id': 'SABA_COURSE_INTERNAL_ID',
        'details': 'XXX',
    }
    SABA_ENROLLMENT = {
        'id': 'SABA_ENROLLMENT_ID',
        'class_id': {
            'id': 'SABA_CLASS_ID',
        },
    }
    SABA_ENROLLMENT_DETAILS = {
        'registrationInfo': {
            'enrollmentStatus': {
                'name': 'INPROGRESS'
            }
        }
    }

    def user_details(self, username=None, **kwargs):  # pylint: disable=unused-argument
        """Mock `user_details`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        if username == 'give_me_a_wrong_result':
            user_details = {
                'no_id_here': 'XXX'
            }
        else:
            user_details = self.SABA_USER_DETAILS
        return user_details

    # pylint: disable=unused-argument
    def get_course_activities(self, *args, **kwargs):
        """Mock ``get_course_activities``

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        for course_activity in [{'id': 'fake_id'}]:
            yield course_activity

    # pylint: disable=unused-argument
    def delete_course_activity(self, *args, **kwargs):
        """Mock ``delete_course_activity``

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        pass

    def get_course_details(self, course_external_id):
        """Mock `get_course_details`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        if course_external_id == 'give_me_a_wrong_result':
            raise SabaApiException(
                http_status=404,
                error_message='Wrong course_id: %s' % course_external_id
            )
        else:
            course_details = self.SABA_COURSE_DETAILS
        return course_details

    def get_enrollment(self, saba_id, *args, **kwargs):  # pylint: disable=unused-argument
        """Mock `get_enrollment`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        if saba_id == 'give_me_a_wrong_result_for_id':
            enrollment_details = copy.copy(self.SABA_ENROLLMENT)
            enrollment_details.pop('id')
        elif saba_id == 'give_me_a_wrong_result_for_class_id':
            enrollment_details = copy.copy(self.SABA_ENROLLMENT)
            enrollment_details.pop('class_id')
        # this one will trigger ``get_enrollment_details(give_me_a_wrong_result)``
        elif saba_id == 'give_me_a_wrong_id_for_details':
            enrollment_details = copy.copy(self.SABA_ENROLLMENT)
            enrollment_details['id'] = 'give_me_a_wrong_result'
        else:
            enrollment_details = self.SABA_ENROLLMENT
        return enrollment_details

    def get_enrollment_details(self, enrollment_id, *args, **kwargs):  # pylint: disable=unused-argument
        """Mock `get_enrollment_details`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``

        """

        if enrollment_id == 'give_me_a_wrong_result':
            enrollment_details = copy.copy(self.SABA_ENROLLMENT_DETAILS)
            enrollment_details['registrationInfo']['enrollmentStatus']['name'] = 'INVALID'
        else:
            enrollment_details = self.SABA_ENROLLMENT_DETAILS

        return enrollment_details

    def mark_attendance(self, *args, **kwargs):
        """Mock `mark_attendance`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        pass

    def complete_enrollment(self, *args, **kwargs):
        """Mock `complete_enrollment`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        pass

    # pylint: disable=unused-argument
    def get_transcripts(self, *args, **kwargs):
        """Mock ``get_transcripts``

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        return [{'id': 'fake_id'}]

    # pylint: disable=unused-argument
    def delete_transcript(self, *args, **kwargs):
        """Mock ``delete_transcript``

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

    def create_transcript(self, *args, **kwargs):
        """Mock `create_transcript`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``"""

        return {
            'successCount': 1,
            'errorCount': 0,
            'successMap': {}
        }

    # pylint: disable=unused-argument
    def course_activity(self, source_user_id, assignee_user_id, course_id):
        """Mock `course_activity`

        For the parameters, see ``mtp.discrete_apps.saba.client.SabaApiClient``

        """

        if course_id != self.SABA_COURSE_DETAILS['id']:
            raise Exception('Wrong course_id: %s' % course_id)


class BaseAssessmentsResultsToSabaTaskTestCase(MTPComplexAssessmentTestCase):
    """Base class for ``AssessmentsResultsToSabaTaskTestCase``"""

    @classmethod
    def setUpTestData(cls):
        """Setup our business objects for tests

        - One ``CertificationProgramUserStatus``:
            - which ``user`` is linked to Saba
            - which ``session.training`` is linked to Saba
            - with attached lessons recommendations linked to Saba

        - A mocked ``SabaApiClient``

        - NoticeType specific to Saba

        - expected returns for failed and successful tasks

        """

        super().setUpTestData()

        # a valid certification with all attached data
        certification = CertificationProgramUserStatusFactory(
            program=cls.certification_program,
            status=cls.certification_statuses[0],
            session=cls.training_session
        )

        # attach a saba external_id to the trainee
        certification.user.external_id = 'XXX'
        certification.user.save()

        # attach a saba external_id to the certification
        with _no_fields_locking(Lockable):
            certification.session.training.original_version._external_id = 'XXX'
            certification.session.training.original_version.save()

        lesson = cls.lessons[0]
        # attach saba external_id to lesson
        reference = LessonReference(
            external_id='XXX',
            external_name='XXX'
        )
        reference.save()
        lesson.reference = reference
        lesson.save()

        # generate recommendations
        for level_name in ['Recommended', 'Mandatory', 'Mastered', 'WHATEVER']:
            level = RecommendationLevelFactory(name=level_name)
            level.save()
            UserLessonRecommendationFactory(
                certification=certification,
                lesson=lesson,
                level=level
            )

        cls.certification_complete_with_data = certification

        cls.mocked_saba_api_client = MockedSabaApiClient()

        NoticeType.create(
            "saba_results",
            "Saba recap email",
            "Send after assessments results are sent to Saba"
        )

        expected_session_date = cls.certification_complete_with_data.session.start_date.date()
        expected_score = cls.certification_complete_with_data.score
        cls.expected_return_ok = {
            'status': str(CertificationProgramUserStatus.SYNC_STATUSES.SENT_OK.display),
            'executed_steps': [
                'parse_certification: OK',
                'get_trainee_details: OK',
                'get_enrollment_details: OK',
                'set_registration: OK',
                'set_exempt_courses: OK',
                'set_mandatory_courses: OK'
            ],
            'steps': {
                'parse_certification': {
                    'success': True,
                    'out': {
                        'session_score': expected_score,
                        'session_start_date': str(expected_session_date),
                        'exempted_courses': [{
                            'local_id': lesson.id,
                            'external_id': 'XXX',
                            'success': False,
                        }, ],
                        'mandatory_courses': [{
                            'local_id': lesson.id,
                            'external_id': 'XXX',
                            'success': False,
                        }, {
                            'local_id': lesson.id,
                            'external_id': 'XXX',
                            'success': False,
                        }],
                        'course_external_id': 'XXX',
                        'saba_trainee_id': 'XXX',
                        'certification_id': cls.certification_complete_with_data.pk,
                        'session_status': 'Level 1'
                    }
                },
                'get_enrollment_details': {
                    'success': True,
                    'out': cls.mocked_saba_api_client.SABA_ENROLLMENT
                },
                'set_registration': {
                    'success': True
                },
                'set_exempt_courses': {
                    'success': True,
                    'out': [{
                        'local_id': lesson.id,
                        'external_id': 'XXX',
                        'internal_id': 'SABA_COURSE_INTERNAL_ID',
                        'response': {
                            'errorCount': 0,
                            'successCount': 1,
                            'successMap': {}
                        },
                        'success': True,
                    }, ],
                },
                'get_trainee_details': {
                    'success': True,
                    'out': cls.mocked_saba_api_client.SABA_USER_DETAILS
                },
                'set_mandatory_courses': {
                    'success': True,
                    'out': [{
                        'local_id': lesson.id,
                        'external_id': 'XXX',
                        'internal_id': 'SABA_COURSE_INTERNAL_ID',
                        'success': True,
                    }, {
                        'local_id': lesson.id,
                        'external_id': 'XXX',
                        'internal_id': 'SABA_COURSE_INTERNAL_ID',
                        'success': True,
                    }],
                }
            }
        }

        cls.expected_return_ko = {
            'steps': {
                'parse_certification': {
                    'success': False
                }
            },
            'executed_steps': ['parse_certification: KO'],
            'status': str(CertificationProgramUserStatus.SYNC_STATUSES.SENT_KO.display),
            'exception': 'User with id %s does not seem to be linked to a SABA user' % (
                cls.trainee.pk
            ),
            'exception_user_message': 'The user does not have a NAO id in NAD. \
                Treat this case manually in NAO as you used to do in the past',
        }

    def setUp(self):
        """Create a fresh test task for each test method run"""

        super().setUp()

        self.task = AssessmentsResultsToSabaTask()
        self.task.saba_api_client = MockedSabaApiClient()
        self.task.data = {}
        self.task.get_django_task = MockedDjangoTask

        # override the certification status names to match test data
        self.task.CERTIFICATION_STATUSES_SUCCESS += ('Level 1', 'Level 2')
        self.task.CERTIFICATION_STATUSES_FAIL += ('Level 3', )
