"""Test the views of the ``trainings`` app"""

from mock import call, patch

from django.contrib import messages
from django.core.urlresolvers import reverse

from mtp.apps.accounts.factories import (
    BusinessUnitFactory,
    GroupRoleFactory,
    JobProfileFactory,
)
from mtp.apps.common.tests.test_cases import DEFAULT_PASSWORD, MTPTestCase, MTPBaseViewsTestCase
from mtp.apps.common.tests.waffle_test_utils import override_switch
from mtp.apps.surveys.factories import PoolFactory, QuestionFactory
from mtp.apps.surveys.models import Question
from mtp.apps.workshops.models import Workshop

from ..constants import ACTIVITY_LAUNCH_ACTION, MODULE_CHOICES
from ..factories import TrainingFactory
from ..models import Training, TrainingSession
from ..views import (
    TrainingListView, TrainingSessionCreate,
    TrainingCreateDraftView
)

from .test_cases import TrainingWithActivitiesTestCase


class BaseTrainingViewsTestCase(MTPBaseViewsTestCase):
    """Base test case to use when testing the training views"""

    @classmethod
    def setUpTestData(cls):
        """Some data needed for the test"""

        super().setUpTestData()

        # Associate the new_trainer with one job_profile
        cls.job_profile = JobProfileFactory()
        GroupRoleFactory(user=cls.new_trainer, group=cls.job_profile)

        # Associate the new_trainer with one business_unit
        business_unit = BusinessUnitFactory()
        GroupRoleFactory(user=cls.new_trainer, group=business_unit)


class TrainingListViewTestCase(BaseTrainingViewsTestCase, TrainingWithActivitiesTestCase):
    """The tests for the list view of the ``Training`` model"""

    def test_training_list_view_as_trainer(self):
        """Test the training list view methods with the "trainer" user"""

        view = self.get_view(view=TrainingListView(), user=self.trainer)

        # This user own one training
        self.assertEqual(len(view.get_queryset()), 1)

    def test_training_list_view_as_new_trainer(self):
        """Test the training list view methods with the "new_trainer" user"""

        view = self.get_view(view=TrainingListView(), user=self.new_trainer)

        # This user don't own any training
        self.assertEqual(len(view.get_queryset()), 0)

    def test_training_list_view_with_business_unit_filter(self):
        """Test the training list view methods with business unit filters"""

        self.login_as_trainer()

        business_unit = BusinessUnitFactory()
        GroupRoleFactory(
            group=business_unit,
            user=self.trainer
        )
        training_list_url = '%s?business_unit=%s' % (
            Training.get_list_url(),
            business_unit.pk
        )

        response = self.client.get(training_list_url)

        # No training associated with this brand new business unit
        self.assertEqual(
            len(response.context['training_list']),
            0
        )

        # Create one using this business unit
        TrainingFactory(owner=self.trainer, business_unit=business_unit)

        response = self.client.get(training_list_url)

        # We got the new training
        self.assertEqual(
            len(response.context['training_list']),
            1
        )

    def test_training_list_view_with_job_profile_filter(self):
        """Test the training list view methods with job profile filters"""

        self.login_as_trainer()

        job_profile = JobProfileFactory()
        GroupRoleFactory(
            group=job_profile,
            user=self.trainer
        )
        training_list_url = '%s?job_profile=%s' % (
            Training.get_list_url(),
            job_profile.pk
        )

        response = self.client.get(training_list_url)

        # No training associated with this brand new job profile
        self.assertEqual(
            len(response.context['training_list']),
            0
        )

        # Create one using this job profile
        TrainingFactory(owner=self.trainer, job_profile=job_profile)

        response = self.client.get(training_list_url)

        # We got the new training
        self.assertEqual(
            len(response.context['training_list']),
            1
        )


class TrainingCreateViewTestCase(TrainingWithActivitiesTestCase):
    """The tests for the create view of the ``Training`` model"""

    def test_owner_should_be_saved(self):
        """Test that the "owner" of a ``Training`` should correctly set when saved"""

        # Ensure the user have no trainings in his collection
        self.assertEqual(len(self.new_trainer.trainings.all()), 0)

        self.client.login(username=self.new_trainer.username,
                          password=DEFAULT_PASSWORD)

        self.client.post(self.training.get_create_url(), {
            'name': 'training name',
            'theme': self.theme.pk
        })

        # Now, the user have one training (because the owner was correctly set)
        self.assertEqual(len(self.new_trainer.trainings.all()), 1)


class TrainingSurveyFlagTestCase(MTPTestCase):
    """Test that the waffle switch `enable_surveys` is used in training views"""

    @classmethod
    def setUpTestData(cls):
        """Create a survey pool with a question"""

        super().setUpTestData()

        QuestionFactory(
            question_type=Question.QUESTION_TYPES.OPEN,
            value_type=Question.VALUE_TYPES.NONE,
            pool=PoolFactory(author=cls.trainer),
        )

        cls.survey_url = cls.training.get_survey_url()

    @override_switch('enable_surveys', False)
    def test_no_survey_buttons_absent_on_trainings_if_surveys_waffle_flag_off(self):
        """Test that the survey buttons are absent if switch is off"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_list_url())

        self.assertFalse(response.context['user_can_have_survey'])
        self.assertNotIn(self.survey_url, str(response.content))

    @override_switch('enable_surveys', True)
    def test_survey_buttons_present_on_trainings_if_surveys_waffle_flag_on(self):
        """Test that the survey buttons are present if switch is on"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_list_url())

        self.assertTrue(response.context['user_can_have_survey'])
        self.assertIn(self.survey_url, str(response.content))

    @override_switch('enable_surveys', False)
    def test_no_survey_button_absent_on_training_if_surveys_waffle_flag_off(self):
        """Test that the survey button is absent if switch is off"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_edit_url())

        self.assertFalse(response.context['user_can_have_survey'])
        self.assertNotIn(self.training.get_survey_url(), str(response.content))

    @override_switch('enable_surveys', True)
    def test_survey_button_present_on_training_if_surveys_waffle_flag_on(self):
        """Test that the survey button is present if switch is on"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_edit_url())

        self.assertTrue(response.context['user_can_have_survey'])
        self.assertIn(self.training.get_survey_url(), str(response.content))


class BaseTrainingEditViewTestCase:
    """Base class to use when testing the views from """

    def test_owner_should_be_accepted(self):
        """Test that the owner can correctly access the view"""

        self.login_as_trainer()

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

        self.assertEqual(response.status_code, 200)

    def test_not_owner_should_be_rejected(self):
        """Test the no-owner cannot access the view"""

        self.login_as_admin()

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

        self.assertEqual(response.status_code, 404)


class TrainingUpdateViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the update view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        cls.url = cls.training.get_edit_url()


class TrainingPublishViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the publish view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because only the drafts can be published
        cls.training = cls.training.create_draft()
        cls.url = cls.training.get_publish_version_state_url()

    def test_publish_view_should_publish_the_training(self):
        """Test that the publish view should correctly publish the training"""

        self.login_as_trainer()
        self.client.post(self.url)

        self.training.refresh_from_db() # We need this

        self.assertEqual(self.training.version_state,
                         self.training.VERSION_STATES.PUBLISHED)


class TrainingCreateDraftViewTestCase(BaseTrainingEditViewTestCase, TrainingWithActivitiesTestCase):
    """The tests for the create draft view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        cls.url = cls.training.get_create_draft_url()

    def test_get_if_not_in_progress_should_allow_creating_a_draft(self):
        """Test that when a draft creation is not in progress, it's marked as allowed in the view"""

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

        self.assertTrue(response.context['create_draft_allowed'])
        self.assertNotIn('create_draft_not_allowed_reason', response.context)

        # We should have the form in the response
        self.assertIn(
            '<form action="%s"' % self.training.get_create_draft_url(),
            str(response.content)
        )

    def test_get_if_in_progress_should_deny_creating_a_draft(self):
        """Test that when a draft creation is in progress, it's marked as allowed in the view"""

        self.login_as_trainer()

        # Simulate a draft creation in progress
        self.training.redis_instance.lock_create_draft(owner=self.training.owner)

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

        self.assertFalse(response.context['create_draft_allowed'])
        self.assertTrue(len(response.context['create_draft_not_allowed_reason']))

        # We should not have the form in the response
        self.assertNotIn(
            '<form action="%s"' % self.training.get_create_draft_url(),
            str(response.content)
        )

    def test_post_if_not_in_progress_should_create_a_draft(self):
        """Test that the create draft view should correctly create a draft."""

        self.login_as_trainer()

        # We'll only check if the ``create_draft`` method is called
        with patch.object(Training, 'create_draft') as patched:
            response = self.client.post(self.url)

        # The method ``create_draft`` should have been called one times, in async mode
        patched.assert_called_once_with(
            async=True,
            skip_in_progress=True,
        )
        # We should have been redirected to the list of trainings
        self.assertRedirects(response, self.training.get_list_url())

        # And we should have a success message
        message_found = False
        for message in messages.get_messages(response.wsgi_request):
            if message.message == TrainingCreateDraftView.success_message:
                message_found = message.level == messages.constants.SUCCESS

        if not message_found:
            self.fail('Expected success message not found')

    def test_post_if_in_progress_should_not_create_a_draft(self):
        """Test that the create draft view should not create a draft if already in progress.

        Notes
        -----
        This notes is copied from the ``post`` method of the ``TrainingCreateDraftView`` view:

        In the ``get`` method we stop the user if a new draft is being created.
        We don't do it in this ``post`` method, by passing ``skip_in_progress=True``, because it
        really should happen very rarely, when the user submits the form twice. So we simply
        ignore and do nothing if a draft creation is already in progress, and give the user its
        success message.

        """

        self.login_as_trainer()

        # Simulate a draft creation in progress
        self.training.redis_instance.lock_create_draft(owner=self.training.owner)

        # We'll only check if the ``create_draft`` method is called
        with patch.object(Training, 'create_draft') as patched:
            response = self.client.post(self.url)

        # The method ``create_draft`` should have been called one times, in async mode
        patched.assert_called_once_with(
            async=True,
            skip_in_progress=True,
        )

        # We should have been redirected to the list of trainings
        self.assertRedirects(response, self.training.get_list_url())

        # And we should have a success message
        message_found = False
        for message in messages.get_messages(response.wsgi_request):
            if message.message == TrainingCreateDraftView.success_message:
                message_found = message.level == messages.constants.SUCCESS

        if not message_found:
            self.fail('Expected success message not found')


class TrainingDuplicateViewTestCase(BaseTrainingEditViewTestCase, TrainingWithActivitiesTestCase):
    """The tests for the training duplicate view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        cls.url = cls.training.get_duplicate_url()

    def test_view_on_get_request(self):
        """Test that we have the correct number of duplicates in the context."""

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

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.template_name[0], 'trainings/training_duplicate.html')

        # We will have 3 duplicate:
        # - for ``self.trainer``, which already have one published (``replace_published=True``)
        # - for ``self.new_trainer`` (will have a ``PUBLISHED`` one)
        # - for ``self.trainer_manager`` (will have a ``DRAFT``)
        self.assertEqual(response.context_data['total_duplicates_nb'], 3)

    def test_view_on_post_request(self):
        """Test that the ``duplicate_to_member`` method is correctly called."""

        self.login_as_trainer()
        # We'll only check if the ``duplicate_to_member`` method is called
        with patch.object(Training, 'duplicate_to_member') as patched:
            response = self.client.post(self.url)

        # The method ``duplicate_to_member`` should have been called three times, in async mode
        patched.assert_has_calls(
            [
                call(
                    new_state=self.training.VERSION_STATES.PUBLISHED,
                    params={
                        'owner_id': self.new_trainer.pk,
                        'business_unit_id': self.business_unit.pk,
                    },
                    async=True,
                    skip_in_progress=True,
                ),
                call(
                    new_state=self.training.VERSION_STATES.PUBLISHED,
                    params={
                        'owner_id': self.trainer.pk,
                        'business_unit_id': self.business_unit.pk,
                    },
                    async=True,
                    skip_in_progress=True,
                ),
                call(
                    new_state=self.training.VERSION_STATES.DRAFT,
                    params={
                        'owner_id': self.trainer_manager.pk,
                        'business_unit_id': self.business_unit_2.pk,
                    },
                    async=True,
                    skip_in_progress=True,
                ),
            ], any_order=True
        )

        self.assertRedirects(response, Training.get_list_url())


class TrainingDeleteViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the training delete view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because only the drafts can be deleted
        cls.training = cls.training.create_draft()
        cls.url = cls.training.get_delete_url()

    def test_delete_view_should_soft_delete_the_training(self):
        """"Test that the delete view should correctly soft-delete the training"""

        self.login_as_trainer()
        self.client.post(self.url)

        self.training.refresh_from_db() # We need this

        # The training is now soft-deleted
        self.assertEqual(self.training.is_deleted, True)


class TrainingOptionsEditViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the edit view of the ``TrainingClientOptions`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because only the drafts options can be edited
        cls.training = cls.training.create_draft()
        cls.url = cls.training.options.get_edit_url()


class TrainingActivityListViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the list view of the ``TrainingActivity`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()
        cls.url = cls.training.get_activity_list_url()


class TrainingActivityDeleteViewTestCase(BaseTrainingEditViewTestCase,
                                         TrainingWithActivitiesTestCase):
    """The tests for the delete view of the ``TrainingActivity`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because we can only edit / delete the activities
        # from a draft training
        cls.training = cls.training.create_draft()
        cls.activity = cls.training.activities.first()
        cls.url = cls.training.activities.first().get_delete_url()

    def test_delete_view_should_soft_delete_the_activity(self):
        """"Test that the delete view should correctly soft-delete the activity"""

        self.login_as_trainer()
        self.client.post(self.url)

        self.activity.refresh_from_db() # We need this

        # The training is now soft-deleted
        self.assertEqual(self.activity.is_deleted, True)


class TrainingActivityDuplicateViewTestCase(BaseTrainingEditViewTestCase,
                                            TrainingWithActivitiesTestCase):
    """The tests for the duplicate view of the ``TrainingActivity`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because we can only duplicate activities
        # from a draft training
        cls.training = cls.training.create_draft()
        cls.activity = cls.training.activities.first()
        cls.url = cls.training.activities.first().get_duplicate_url()

    def test_duplicate_view_should_duplicate_the_activity(self):
        """"Test that the duplicate view should correctly duplicate the activity"""

        # Initial activities
        activities_count = self.training.activities.count()

        self.login_as_trainer()
        self.client.post(self.url)

        # The training got one more activity
        self.assertEqual(self.training.activities.count(),
                         activities_count+1)


class TrainingSessionListViewTestCase(MTPTestCase):
    """The tests for the list view of the ``TrainingSession`` model"""

    def test_session_list_view_without_training_for_trainer(self):
        """Test the output of the session list methods without a training filter"""

        self.login_as_trainer()

        # Call the view with the trainer user
        response = self.client.get(reverse('trainings:session-list'))

        self.assertEqual(
            response.status_code,
            200
        )

        # The trainer see one session (for the training he owns)
        self.assertEqual(
            len(response.context['trainingsession_list']),
            1
        )

    def test_session_list_view_without_training_for_trainee(self):
        """Test the output of the session list methods without a training filter"""

        self.login_as_trainee()

        # Call the view with the trainee user
        response = self.client.get(reverse('trainings:session-list'))

        self.assertEqual(
            response.status_code,
            200
        )

        # The trainee see one session (he was added into)
        self.assertEqual(
            len(response.context['trainingsession_list']),
            1
        )

    def test_session_list_view_with_job_profile_manager(self):
        """Test that we have a ``business_unit`` filter with a job profile manager"""

        self.login_as_admin()

        # Call the view with the admin user
        response = self.client.get(reverse('trainings:session-list'))

        self.assertEqual(
            response.status_code,
            200
        )

        # The business unit field is present
        self.assertIn(
            'id_training__business_unit',
            response.content.decode('utf-8')
        )


class TrainingSessionEditViewsTestCase(MTPBaseViewsTestCase, MTPTestCase):
    """The tests for the edit views of the ``TrainingSession`` model"""

    def setUp(self):
        """Login as a trainer"""

        super().setUp()

        self.login_as_trainer()

    def test_all_routes_should_work(self):
        """Test the response_code of all the session edit routes"""

        urls_to_test = [
            self.training_session.get_edit_url(),
            self.training_session.get_delete_url(),
            self.training_session.get_whoswho_url(),
            self.training_session.get_close_url(),
            self.training_session.get_create_url(),
            self.training_session.get_podium_url(),
            self.training.get_session_create_url(),
        ]

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

    def test_session_create_view_without_a_training_as_a_trainer(self):
        """Test the behaviour of the session create view, as a trainer"""

        view = self.get_view(view=TrainingSessionCreate(), user=self.trainer)

        self.assertEqual(
            view.get_form_kwargs().get('remove_trainer_field'),
            True
        )
        self.assertEqual(
            view.get_form_kwargs().get('current_user'),
            self.trainer
        )

    def test_session_create_view_without_a_training_as_an_admin(self):
        """Test the behaviour of the session create view, as an admin"""

        view = self.get_view(view=TrainingSessionCreate(), user=self.admin)

        self.assertEqual(
            view.get_form_kwargs().get('remove_trainer_field'),
            False
        )
        self.assertEqual(
            view.get_form_kwargs().get('current_user'),
            self.admin
        )

    def test_save_the_session_should_set_the_trainer_as_a_trainer(self):
        """Test that the create session, as a trainer, should set the trainer"""

        # One already created session
        self.assertEqual(
            TrainingSession.objects.filter(trainer=self.trainer).count(),
            1
        )

        response = self.client.post(self.training.get_session_create_url(), {
            'training': self.training.pk,
            'start_date': '2015-01-01 18:30',
            'end_date': '2015-01-01 18:30',
            'state': 'du',
            'permissions': 'op'
        }, follow=True)

        self.assertEqual(
            response.status_code,
            200  # Redirected after the save
        )

        # The trainer now got two sessions
        self.assertEqual(
            TrainingSession.objects.filter(trainer=self.trainer).count(),
            2
        )

    def test_save_the_session_should_not_set_the_trainer_as_an_admin(self):
        """Test that editing a session, as an admin, should not modify the trainer"""

        # The session is associated with the trainer
        self.assertEqual(
            self.training_session.trainer_id,
            self.trainer.id
        )

        self.login_as_admin()

        # Modify the session
        response = self.client.post(self.training_session.get_edit_url(), {
            'start_date': '2015-01-01 18:30',
            'end_date': '2015-01-01 18:30',
            'state': 'du',
            'permissions': 'op'
        }, follow=True)

        self.assertEqual(
            response.status_code,
            200  # Redirected after the save
        )

        self.training_session.refresh_from_db()

        # The session is still associated with the trainer
        self.assertEqual(
            self.training_session.trainer_id,
            self.trainer.id
        )

    def test_close_session_view_should_close_the_session(self):
        """Test that the close session view should correctly close the session"""

        self.assertEqual(self.training_session.state,
                         self.training_session.STATE_CHOICES.DURING)

        self.client.post(self.training_session.get_close_url())

        self.training_session.refresh_from_db()  # Necessary

        self.assertEqual(self.training_session.state,
                         self.training_session.STATE_CHOICES.CLOSED)


class ActivityQRCodeDownloadViewTestCase(TrainingWithActivitiesTestCase):
    """Test case that tests the launch QRCode download."""

    def test_get_qrcode_data(self):
        """Test get qrcode data which be stored in QRCode."""

        activity = self.training.activities.instance_of(Workshop).first()
        qrcode_data = activity.get_qrcode_data()

        self.assertEqual(qrcode_data['id'], activity.id)
        self.assertEqual(qrcode_data['type'], MODULE_CHOICES.WORKSHOP)
        self.assertEqual(qrcode_data['action'], ACTIVITY_LAUNCH_ACTION)
        self.assertEqual(qrcode_data['original_version'], activity.id)

        duplicated_activity = activity.duplicate()
        qrcode_data = duplicated_activity.get_qrcode_data()

        self.assertEqual(qrcode_data['id'], duplicated_activity.id)
        self.assertEqual(qrcode_data['type'], MODULE_CHOICES.WORKSHOP)
        self.assertEqual(qrcode_data['action'], ACTIVITY_LAUNCH_ACTION)
        self.assertEqual(qrcode_data['original_version'], duplicated_activity.original_version_id)

    def test_qrcode_download_detail_view_work(self):
        """Test that the download detail view of QR Code work."""

        activity = self.training.activities.instance_of(Workshop).first()
        url = activity.get_qrcode_url()

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

        self.assertEqual(response.status_code, 200)

    def test_qrcode_download_list_view_work(self):
        """Test that the download list view of QR Code work."""

        url = self.training.get_qrcodes_url()

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

        self.assertEqual(response.status_code, 200)
