# coding=UTF-8
"""
tests for overrides
"""
import datetime

import mock
import pytz
from ccx_keys.locator import CCXLocator
from django.test.utils import override_settings
from nose.plugins.attrib import attr

from courseware.courses import get_course_by_id
from courseware.field_overrides import OverrideFieldData
from courseware.testutils import FieldOverrideTestMixin
from lms.djangoapps.ccx.models import CustomCourseForEdX
from lms.djangoapps.ccx.overrides import override_field_for_ccx
from lms.djangoapps.ccx.tests.utils import flatten, iter_blocks
from lms.djangoapps.courseware.tests.test_field_overrides import inject_field_overrides
from request_cache.middleware import RequestCache
from student.tests.factories import AdminFactory
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory


@attr(shard=1)
@override_settings(
    XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
    MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['ccx.overrides.CustomCoursesForEdxOverrideProvider'],
)
class TestFieldOverrides(FieldOverrideTestMixin, SharedModuleStoreTestCase):
    """
    Make sure field overrides behave in the expected manner.
    """
    MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

    @classmethod
    def setUpClass(cls):
        """
        Course is created here and shared by all the class's tests.
        """
        super(TestFieldOverrides, cls).setUpClass()
        cls.course = CourseFactory.create()
        cls.course.enable_ccx = True

        # Create a course outline
        start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC)
        due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC)
        chapters = [ItemFactory.create(start=start, parent=cls.course)
                    for _ in xrange(2)]
        sequentials = flatten([
            [ItemFactory.create(parent=chapter) for _ in xrange(2)]
            for chapter in chapters])
        verticals = flatten([
            [ItemFactory.create(due=due, parent=sequential) for _ in xrange(2)]
            for sequential in sequentials])
        blocks = flatten([  # pylint: disable=unused-variable
            [ItemFactory.create(parent=vertical) for _ in xrange(2)]
            for vertical in verticals])

    def setUp(self):
        """
        Set up tests
        """
        super(TestFieldOverrides, self).setUp()

        self.ccx = ccx = CustomCourseForEdX(
            course_id=self.course.id,
            display_name='Test CCX',
            coach=AdminFactory.create())
        ccx.save()

        patch = mock.patch('ccx.overrides.get_current_ccx')
        self.get_ccx = get_ccx = patch.start()
        get_ccx.return_value = ccx
        self.addCleanup(patch.stop)

        self.addCleanup(RequestCache.clear_request_cache)

        inject_field_overrides(iter_blocks(ccx.course), self.course, AdminFactory.create())

        self.ccx_key = CCXLocator.from_course_locator(self.course.id, ccx.id)
        self.ccx_course = get_course_by_id(self.ccx_key, depth=None)

        def cleanup_provider_classes():
            """
            After everything is done, clean up by un-doing the change to the
            OverrideFieldData object that is done during the wrap method.
            """
            OverrideFieldData.provider_classes = None
        self.addCleanup(cleanup_provider_classes)

    def test_override_start(self):
        """
        Test that overriding start date on a chapter works.
        """
        ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
        chapter = self.ccx_course.get_children()[0]
        override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
        self.assertEquals(chapter.start, ccx_start)

    def test_override_num_queries_new_field(self):
        """
        Test that for creating new field executed only create query
        """
        ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
        chapter = self.ccx_course.get_children()[0]
        # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the
        # transaction.atomic decorator wrapping override_field_for_ccx.
        # One SELECT and one INSERT.
        # One inner SAVEPOINT/RELEASE SAVEPOINT pair around the INSERT caused by the
        # transaction.atomic down in Django's get_or_create()/_create_object_from_params().
        with self.assertNumQueries(6):
            override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)

    def test_override_num_queries_update_existing_field(self):
        """
        Test that overriding existing field executed create, fetch and update queries.
        """
        ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
        new_ccx_start = datetime.datetime(2015, 12, 25, 00, 00, tzinfo=pytz.UTC)
        chapter = self.ccx_course.get_children()[0]
        override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
        with self.assertNumQueries(3):
            override_field_for_ccx(self.ccx, chapter, 'start', new_ccx_start)

    def test_override_num_queries_field_value_not_changed(self):
        """
        Test that if value of field does not changed no query execute.
        """
        ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
        chapter = self.ccx_course.get_children()[0]
        override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
        with self.assertNumQueries(2):      # 2 savepoints
            override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)

    def test_overriden_field_access_produces_no_extra_queries(self):
        """
        Test no extra queries when accessing an overriden field more than once.
        """
        ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
        chapter = self.ccx_course.get_children()[0]
        # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the
        # transaction.atomic decorator wrapping override_field_for_ccx.
        # One SELECT and one INSERT.
        # One inner SAVEPOINT/RELEASE SAVEPOINT pair around the INSERT caused by the
        # transaction.atomic down in Django's get_or_create()/_create_object_from_params().
        with self.assertNumQueries(6):
            override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)

    def test_override_is_inherited(self):
        """
        Test that sequentials inherit overridden start date from chapter.
        """
        ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
        chapter = self.ccx_course.get_children()[0]
        override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
        self.assertEquals(chapter.get_children()[0].start, ccx_start)
        self.assertEquals(chapter.get_children()[1].start, ccx_start)

    def test_override_is_inherited_even_if_set_in_mooc(self):
        """
        Test that a due date set on a chapter is inherited by grandchildren
        (verticals) even if a due date is set explicitly on grandchildren in
        the mooc.
        """
        ccx_due = datetime.datetime(2015, 1, 1, 00, 00, tzinfo=pytz.UTC)
        chapter = self.ccx_course.get_children()[0]
        chapter.display_name = 'itsme!'
        override_field_for_ccx(self.ccx, chapter, 'due', ccx_due)
        vertical = chapter.get_children()[0].get_children()[0]
        self.assertEqual(vertical.due, ccx_due)
