# -*- coding: utf-8 -*-
"""
End-to-end tests for the LMS that utilize the
progress page.
"""
from contextlib import contextmanager

import ddt

from ..helpers import (
    UniqueCourseTest, auto_auth, create_multiple_choice_problem, create_multiple_choice_xml, get_modal_alert
)
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
from ...pages.common.logout import LogoutPage
from ...pages.lms.courseware import CoursewarePage
from ...pages.lms.instructor_dashboard import InstructorDashboardPage, StudentSpecificAdmin
from ...pages.lms.problem import ProblemPage
from ...pages.lms.progress import ProgressPage
from ...pages.studio.component_editor import ComponentEditorView
from ...pages.studio.utils import type_in_codemirror
from ...pages.studio.overview import CourseOutlinePage


class ProgressPageBaseTest(UniqueCourseTest):
    """
    Provides utility methods for tests retrieving
    scores from the progress page.
    """
    USERNAME = "STUDENT_TESTER"
    EMAIL = "student101@example.com"
    SECTION_NAME = 'Test Section 1'
    SUBSECTION_NAME = 'Test Subsection 1'
    UNIT_NAME = 'Test Unit 1'
    PROBLEM_NAME = 'Test Problem 1'
    PROBLEM_NAME_2 = 'Test Problem 2'

    def setUp(self):
        super(ProgressPageBaseTest, self).setUp()
        self.courseware_page = CoursewarePage(self.browser, self.course_id)
        self.problem_page = ProblemPage(self.browser)  # pylint: disable=attribute-defined-outside-init
        self.progress_page = ProgressPage(self.browser, self.course_id)
        self.logout_page = LogoutPage(self.browser)

        self.course_outline = CourseOutlinePage(
            self.browser,
            self.course_info['org'],
            self.course_info['number'],
            self.course_info['run']
        )

        # Install a course with problems
        self.course_fix = CourseFixture(
            self.course_info['org'],
            self.course_info['number'],
            self.course_info['run'],
            self.course_info['display_name']
        )

        self.problem1 = create_multiple_choice_problem(self.PROBLEM_NAME)
        self.problem2 = create_multiple_choice_problem(self.PROBLEM_NAME_2)

        self.course_fix.add_children(
            XBlockFixtureDesc('chapter', self.SECTION_NAME).add_children(
                XBlockFixtureDesc('sequential', self.SUBSECTION_NAME).add_children(
                    XBlockFixtureDesc('vertical', self.UNIT_NAME).add_children(self.problem1, self.problem2)
                )
            )
        ).install()

        # Auto-auth register for the course.
        auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)

    def _answer_problem_correctly(self):
        """
        Submit a correct answer to the problem.
        """
        self._answer_problem(choice=2)

    def _answer_problem(self, choice):
        """
        Submit the given choice for the problem.
        """
        self.courseware_page.go_to_sequential_position(1)
        self.problem_page.click_choice('choice_choice_{}'.format(choice))
        self.problem_page.click_submit()

    def _get_section_score(self):
        """
        Return a list of scores from the progress page.
        """
        self.progress_page.visit()
        return self.progress_page.section_score(self.SECTION_NAME, self.SUBSECTION_NAME)

    def _get_problem_scores(self):
        """
        Return a list of scores from the progress page.
        """
        self.progress_page.visit()
        return self.progress_page.scores(self.SECTION_NAME, self.SUBSECTION_NAME)

    @contextmanager
    def _logged_in_session(self, staff=False):
        """
        Ensure that the user is logged in and out appropriately at the beginning
        and end of the current test.
        """
        self.logout_page.visit()
        try:
            if staff:
                auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id)
            else:
                auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id)
            yield
        finally:
            self.logout_page.visit()


@ddt.ddt
class PersistentGradesTest(ProgressPageBaseTest):
    """
    Test that grades for completed assessments are persisted
    when various edits are made.
    """
    def setUp(self):
        super(PersistentGradesTest, self).setUp()
        self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)

    def _change_subsection_structure(self):
        """
        Adds a unit to the subsection, which
        should not affect a persisted subsection grade.
        """
        self.course_outline.visit()
        subsection = self.course_outline.section(self.SECTION_NAME).subsection(self.SUBSECTION_NAME)
        subsection.expand_subsection()
        subsection.add_unit()
        subsection.publish()

    def _set_staff_lock_on_subsection(self, locked):
        """
        Sets staff lock for a subsection, which should hide the
        subsection score from students on the progress page.
        """
        self.course_outline.visit()
        subsection = self.course_outline.section_at(0).subsection_at(0)
        subsection.set_staff_lock(locked)
        self.assertEqual(subsection.has_staff_lock_warning, locked)

    def _get_problem_in_studio(self):
        """
        Returns the editable problem component in studio,
        along with its container unit, so any changes can
        be published.
        """
        self.course_outline.visit()
        self.course_outline.section_at(0).subsection_at(0).expand_subsection()
        unit = self.course_outline.section_at(0).subsection_at(0).unit(self.UNIT_NAME).go_to()
        component = unit.xblocks[1]
        return unit, component

    def _change_weight_for_problem(self):
        """
        Changes the weight of the problem, which should not affect
        persisted grades.
        """
        unit, component = self._get_problem_in_studio()
        component.edit()
        component_editor = ComponentEditorView(self.browser, component.locator)
        component_editor.set_field_value_and_save('Problem Weight', 5)
        unit.publish()

    def _change_correct_answer_for_problem(self, new_correct_choice=1):
        """
        Changes the correct answer of the problem.
        """
        unit, component = self._get_problem_in_studio()
        modal = component.edit()

        modified_content = create_multiple_choice_xml(correct_choice=new_correct_choice)

        type_in_codemirror(self, 0, modified_content)
        modal.q(css='.action-save').click()
        unit.publish()

    def _student_admin_action_for_problem(self, action_button, has_cancellable_alert=False):
        """
        As staff, clicks the "delete student state" button,
        deleting the student user's state for the problem.
        """
        self.instructor_dashboard_page.visit()
        student_admin_section = self.instructor_dashboard_page.select_student_admin(StudentSpecificAdmin)
        student_admin_section.set_student_email_or_username(self.USERNAME)
        student_admin_section.set_problem_location(self.problem1.locator)
        getattr(student_admin_section, action_button).click()
        if has_cancellable_alert:
            alert = get_modal_alert(student_admin_section.browser)
            alert.accept()
        alert = get_modal_alert(student_admin_section.browser)
        alert.dismiss()
        return student_admin_section

    def test_progress_page_shows_scored_problems(self):
        """
        Checks the progress page before and after answering
        the course's first problem correctly.
        """
        with self._logged_in_session():
            self.assertEqual(self._get_problem_scores(), [(0, 1), (0, 1)])
            self.assertEqual(self._get_section_score(), (0, 2))
            self.courseware_page.visit()
            self._answer_problem_correctly()
            self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
            self.assertEqual(self._get_section_score(), (1, 2))

    @ddt.data(
        _change_correct_answer_for_problem,
        _change_subsection_structure,
        _change_weight_for_problem
    )
    def test_content_changes_do_not_change_score(self, edit):
        with self._logged_in_session():
            self.courseware_page.visit()
            self._answer_problem_correctly()

        with self._logged_in_session(staff=True):
            edit(self)

        with self._logged_in_session():
            self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
            self.assertEqual(self._get_section_score(), (1, 2))

    def test_visibility_change_affects_score(self):
        with self._logged_in_session():
            self.courseware_page.visit()
            self._answer_problem_correctly()

        with self._logged_in_session(staff=True):
            self._set_staff_lock_on_subsection(True)

        with self._logged_in_session():
            self.assertEqual(self._get_problem_scores(), None)
            self.assertEqual(self._get_section_score(), None)

        with self._logged_in_session(staff=True):
            self._set_staff_lock_on_subsection(False)

        with self._logged_in_session():
            self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
            self.assertEqual(self._get_section_score(), (1, 2))

    def test_delete_student_state_affects_score(self):
        with self._logged_in_session():
            self.courseware_page.visit()
            self._answer_problem_correctly()

        with self._logged_in_session(staff=True):
            self._student_admin_action_for_problem('delete_state_button', has_cancellable_alert=True)

        with self._logged_in_session():
            self.assertEqual(self._get_problem_scores(), [(0, 1), (0, 1)])
            self.assertEqual(self._get_section_score(), (0, 2))


class SubsectionGradingPolicyTest(ProgressPageBaseTest):
    """
    Tests changing a subsection's 'graded' field
    and the effect it has on the progress page.
    """
    def setUp(self):
        super(SubsectionGradingPolicyTest, self).setUp()
        self._set_policy_for_subsection("Homework")

    def _set_policy_for_subsection(self, policy):
        """
        Set the grading policy for the
        subsection in the test.
        """
        with self._logged_in_session(staff=True):
            self.course_outline.visit()
            modal = self.course_outline.section_at(0).subsection_at(0).edit()
            modal.policy = policy
            modal.save()

    def _check_scores_and_page_text(self, problem_scores, section_score, text):
        """
        Asserts that the given problem and section scores, and text,
        appear on the progress page.
        """
        self.assertEqual(self._get_problem_scores(), problem_scores)
        self.assertEqual(self._get_section_score(), section_score)
        self.assertTrue(self.progress_page.text_on_page(text))

    def test_subsection_grading_policy_on_progress_page(self):
        with self._logged_in_session():
            self._check_scores_and_page_text([(0, 1), (0, 1)], (0, 2), "Homework 1 - Test Subsection 1 - 0% (0/2)")
            self.courseware_page.visit()
            self._answer_problem_correctly()
            self._check_scores_and_page_text([(1, 1), (0, 1)], (1, 2), "Homework 1 - Test Subsection 1 - 50% (1/2)")

        self._set_policy_for_subsection("Not Graded")

        with self._logged_in_session():
            self.progress_page.visit()
            self.assertEqual(self._get_problem_scores(), [(1, 1), (0, 1)])
            self.assertEqual(self._get_section_score(), (1, 2))
            self.assertFalse(self.progress_page.text_on_page("Homework 1 - Test Subsection 1"))

        self._set_policy_for_subsection("Homework")
        with self._logged_in_session():
            self._check_scores_and_page_text([(1, 1), (0, 1)], (1, 2), "Homework 1 - Test Subsection 1 - 50% (1/2)")
