"""Tests for self-paced course due date overrides."""
# pylint: disable=missing-docstring
import datetime
import pytz

from django.test.utils import override_settings
from mock import patch

from courseware.tests.factories import BetaTesterFactory
from courseware.access import has_access
from lms.djangoapps.ccx.tests.test_overrides import inject_field_overrides
from lms.djangoapps.django_comment_client.utils import get_accessible_discussion_modules
from lms.djangoapps.courseware.field_overrides import OverrideFieldData, OverrideModulestoreFieldData
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory


@override_settings(
    XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
    MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['courseware.self_paced_overrides.SelfPacedDateOverrideProvider'],
)
class SelfPacedDateOverrideTest(ModuleStoreTestCase):
    """
    Tests for self-paced due date overrides.
    """

    def setUp(self):
        self.reset_setting_cache_variables()
        super(SelfPacedDateOverrideTest, self).setUp()

        SelfPacedConfiguration(enabled=True).save()

        self.non_staff_user, __ = self.create_non_staff_user()
        self.now = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
        self.future = self.now + datetime.timedelta(days=30)

    def tearDown(self):
        self.reset_setting_cache_variables()
        super(SelfPacedDateOverrideTest, self).tearDown()

    def reset_setting_cache_variables(self):
        """
        The overridden settings for this class get cached on class variables.
        Reset those to None before and after running the test to ensure clean
        behavior.
        """
        OverrideFieldData.provider_classes = None
        OverrideModulestoreFieldData.provider_classes = None

    def setup_course(self, **course_kwargs):
        """Set up a course with provided course attributes.

        Creates a child block with a due date, and ensures that field
        overrides are correctly applied for both blocks.
        """
        course = CourseFactory.create(**course_kwargs)
        section = ItemFactory.create(parent=course, due=self.now)
        inject_field_overrides((course, section), course, self.user)
        return (course, section)

    def create_discussion_modules(self, parent):
        # Create a released discussion module
        ItemFactory.create(
            parent=parent,
            category='discussion',
            display_name='released',
            start=self.now,
        )

        # Create a scheduled discussion module
        ItemFactory.create(
            parent=parent,
            category='discussion',
            display_name='scheduled',
            start=self.future,
        )

    def test_instructor_paced_due_date(self):
        __, ip_section = self.setup_course(display_name="Instructor Paced Course", self_paced=False)
        self.assertEqual(ip_section.due, self.now)

    def test_self_paced_due_date(self):
        __, sp_section = self.setup_course(display_name="Self-Paced Course", self_paced=True)
        self.assertIsNone(sp_section.due)

    def test_self_paced_disabled_due_date(self):
        SelfPacedConfiguration(enabled=False).save()
        __, sp_section = self.setup_course(display_name="Self-Paced Course", self_paced=True)
        self.assertEqual(sp_section.due, self.now)

    @patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_course_access_to_beta_users(self):
        """
        Test that beta testers can access `self_paced` course prior to start date.
        """
        now = datetime.datetime.now(pytz.UTC)
        one_month_from_now = now + datetime.timedelta(days=30)
        course_options = {
            'days_early_for_beta': 100,
            'self_paced': True,
            'start': one_month_from_now,
        }
        # Create a `self_paced` course and add a beta tester in it
        self_paced_course, self_paced_section = self.setup_course(**course_options)
        beta_tester = BetaTesterFactory(course_key=self_paced_course.id)

        # Verify course is `self_paced` and course has start date but not section.
        self.assertTrue(self_paced_course.self_paced)
        self.assertEqual(self_paced_course.start, one_month_from_now)
        self.assertIsNone(self_paced_section.start)

        # Verify that non-staff user do not have access to the course
        self.assertFalse(has_access(self.non_staff_user, 'load', self_paced_course))

        # Verify beta tester can access the course as well as the course sections
        self.assertTrue(has_access(beta_tester, 'load', self_paced_course))
        self.assertTrue(has_access(beta_tester, 'load', self_paced_section, self_paced_course.id))

    @patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_instructor_paced_discussion_module_visibility(self):
        """
        Verify that discussion modules scheduled for release in the future are
        not visible to students in an instructor-paced course.
        """
        course, section = self.setup_course(start=self.now, self_paced=False)
        self.create_discussion_modules(section)

        # Only the released module should be visible when the course is instructor-paced.
        modules = get_accessible_discussion_modules(course, self.non_staff_user)
        self.assertTrue(
            all(module.display_name == 'released' for module in modules)
        )

    @patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False})
    def test_self_paced_discussion_module_visibility(self):
        """
        Regression test. Verify that discussion modules scheduled for release
        in the future are visible to students in a self-paced course.
        """
        course, section = self.setup_course(start=self.now, self_paced=True)
        self.create_discussion_modules(section)

        # The scheduled module should be visible when the course is self-paced.
        modules = get_accessible_discussion_modules(course, self.non_staff_user)
        self.assertEqual(len(modules), 2)
        self.assertTrue(
            any(module.display_name == 'scheduled' for module in modules)
        )
