"""
Unit tests for masquerade.
"""
import json
from mock import patch
from nose.plugins.attrib import attr
from datetime import datetime

from django.core.urlresolvers import reverse
from django.utils.timezone import UTC

from capa.tests.response_xml_factory import OptionResponseXMLFactory
from courseware.masquerade import handle_ajax, setup_masquerade, get_masquerading_group_info
from courseware.tests.factories import StaffFactory
from courseware.tests.helpers import LoginEnrollmentTestCase, get_request_for_user
from student.tests.factories import UserFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from xmodule.partitions.partitions import Group, UserPartition


class MasqueradeTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
    """
    Base class for masquerade tests that sets up a test course and enrolls a user in the course.
    """
    def setUp(self):
        super(MasqueradeTestCase, self).setUp()

        # By default, tests run with DISABLE_START_DATES=True. To test that masquerading as a student is
        # working properly, we must use start dates and set a start date in the past (otherwise the access
        # checks exist prematurely).
        self.course = CourseFactory.create(number='masquerade-test', metadata={'start': datetime.now(UTC())})
        self.chapter = ItemFactory.create(
            parent_location=self.course.location,
            category="chapter",
            display_name="Test Section",
        )
        self.sequential_display_name = "Test Masquerade Subsection"
        self.sequential = ItemFactory.create(
            parent_location=self.chapter.location,
            category="sequential",
            display_name=self.sequential_display_name,
        )
        self.vertical = ItemFactory.create(
            parent_location=self.sequential.location,
            category="vertical",
            display_name="Test Unit",
        )
        problem_xml = OptionResponseXMLFactory().build_xml(
            question_text='The correct answer is Correct',
            num_inputs=2,
            weight=2,
            options=['Correct', 'Incorrect'],
            correct_option='Correct'
        )
        self.problem_display_name = "Test Masquerade Problem"
        self.problem = ItemFactory.create(
            parent_location=self.vertical.location,
            category='problem',
            data=problem_xml,
            display_name=self.problem_display_name
        )
        self.test_user = self.create_user()
        self.login(self.test_user.email, 'test')
        self.enroll(self.course, True)

    def get_courseware_page(self):
        """
        Returns the server response for the courseware page.
        """
        url = reverse(
            'courseware_section',
            kwargs={
                'course_id': unicode(self.course.id),
                'chapter': self.chapter.location.name,
                'section': self.sequential.location.name,
            }
        )
        return self.client.get(url)

    def _create_mock_json_request(self, user, body, method='POST', session=None):
        """
        Returns a mock JSON request for the specified user
        """
        request = get_request_for_user(user)
        request.method = method
        request.META = {'CONTENT_TYPE': ['application/json']}
        request.body = body
        request.session = session or {}
        return request

    def verify_staff_debug_present(self, staff_debug_expected):
        """
        Verifies that the staff debug control visibility is as expected (for staff only).
        """
        content = self.get_courseware_page().content
        self.assertTrue(self.sequential_display_name in content, "Subsection should be visible")
        self.assertEqual(staff_debug_expected, 'Staff Debug Info' in content)

    def get_problem(self):
        """
        Returns the JSON content for the problem in the course.
        """
        problem_url = reverse(
            'xblock_handler',
            kwargs={
                'course_id': unicode(self.course.id),
                'usage_id': unicode(self.problem.location),
                'handler': 'xmodule_handler',
                'suffix': 'problem_get'
            }
        )
        return self.client.get(problem_url)

    def verify_show_answer_present(self, show_answer_expected):
        """
        Verifies that "Show Answer" is only present when expected (for staff only).
        """
        problem_html = json.loads(self.get_problem().content)['html']
        self.assertTrue(self.problem_display_name in problem_html)
        self.assertEqual(show_answer_expected, "Show Answer" in problem_html)


@attr('shard_1')
class NormalStudentVisibilityTest(MasqueradeTestCase):
    """
    Verify the course displays as expected for a "normal" student (to ensure test setup is correct).
    """
    def create_user(self):
        """
        Creates a normal student user.
        """
        return UserFactory()

    @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_staff_debug_not_visible(self):
        """
        Tests that staff debug control is not present for a student.
        """
        self.verify_staff_debug_present(False)

    @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_show_answer_not_visible(self):
        """
        Tests that "Show Answer" is not visible for a student.
        """
        self.verify_show_answer_present(False)


class StaffMasqueradeTestCase(MasqueradeTestCase):
    """
    Base class for tests of the masquerade behavior for a staff member.
    """
    def create_user(self):
        """
        Creates a staff user.
        """
        return StaffFactory(course_key=self.course.id)

    def update_masquerade(self, role, group_id=None):
        """
        Toggle masquerade state.
        """
        masquerade_url = reverse(
            'masquerade_update',
            kwargs={
                'course_key_string': unicode(self.course.id),
            }
        )
        response = self.client.post(
            masquerade_url,
            json.dumps({"role": role, "group_id": group_id}),
            "application/json"
        )
        self.assertEqual(response.status_code, 204)
        return response


@attr('shard_1')
class TestStaffMasqueradeAsStudent(StaffMasqueradeTestCase):
    """
    Check for staff being able to masquerade as student.
    """
    @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_staff_debug_with_masquerade(self):
        """
        Tests that staff debug control is not visible when masquerading as a student.
        """
        # Verify staff initially can see staff debug
        self.verify_staff_debug_present(True)

        # Toggle masquerade to student
        self.update_masquerade(role='student')
        self.verify_staff_debug_present(False)

        # Toggle masquerade back to staff
        self.update_masquerade(role='staff')
        self.verify_staff_debug_present(True)

    @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_show_answer_for_staff(self):
        """
        Tests that "Show Answer" is not visible when masquerading as a student.
        """
        # Verify that staff initially can see "Show Answer".
        self.verify_show_answer_present(True)

        # Toggle masquerade to student
        self.update_masquerade(role='student')
        self.verify_show_answer_present(False)

        # Toggle masquerade back to staff
        self.update_masquerade(role='staff')
        self.verify_show_answer_present(True)


@attr('shard_1')
class TestGetMasqueradingGroupId(StaffMasqueradeTestCase):
    """
    Check for staff being able to masquerade as belonging to a group.
    """
    def setUp(self):
        super(TestGetMasqueradingGroupId, self).setUp()
        self.user_partition = UserPartition(
            0, 'Test User Partition', '',
            [Group(0, 'Group 1'), Group(1, 'Group 2')],
            scheme_id='cohort'
        )
        self.course.user_partitions.append(self.user_partition)
        modulestore().update_item(self.course, self.test_user.id)

    @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_group_masquerade(self):
        """
        Tests that a staff member can masquerade as being in a particular group.
        """
        # Verify that there is no masquerading group initially
        group_id, user_partition_id = get_masquerading_group_info(self.test_user, self.course.id)
        self.assertIsNone(group_id)
        self.assertIsNone(user_partition_id)

        # Install a masquerading group
        request = self._create_mock_json_request(
            self.test_user,
            body='{"role": "student", "user_partition_id": 0, "group_id": 1}'
        )
        handle_ajax(request, unicode(self.course.id))
        setup_masquerade(request, self.test_user, True)

        # Verify that the masquerading group is returned
        group_id, user_partition_id = get_masquerading_group_info(self.test_user, self.course.id)
        self.assertEqual(group_id, 1)
        self.assertEqual(user_partition_id, 0)