from django.test import RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id, from_global_id

from api.schema import schema
from api.utils import get_object
from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory
from books.models import Snapshot, ChapterSnapshot
from books.tests.queries import MODULE_QUERY, SNAPSHOT_MODULE_QUERY, CREATE_SNAPSHOT_MUTATION, APPLY_SNAPSHOT_MUTATION, \
    MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION
from core.tests.base_test import SkillboxTestCase
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
from users.factories import SchoolClassFactory
from users.models import User, SchoolClass


class CreateSnapshotTestCase(SkillboxTestCase):
    def _test_objective(self, objective, text, hidden):
        self.assertEqual(objective['text'], text)
        self.assertEqual(objective['hidden'], hidden)

    def _test_content_block(self, content_block, title, hidden):
        self.assertEqual(content_block['title'], title)
        self.assertEqual(content_block['hidden'], hidden)

    @property
    def graphene_client(self):
        return self.get_client()

    def setUp(self):
        self.createDefault()
        self.client = self.get_client()
        # teacher will create snapshot
        self.slug = 'some-module'

        self.module = ModuleFactory(slug=self.slug)
        self.skillbox_class = SchoolClass.objects.get(name='skillbox')
        self.teacher2 = User.objects.get(username='teacher2')
        self.second_class_name = 'second_class'
        self.second_class = SchoolClass.objects.get(name=self.second_class_name)
        self.admin = User.objects.get(username='admin')

        # module M has a chapter
        self.chapter = ChapterFactory(parent=self.module, slug='some-chapter', owner=self.admin)
        ChapterFactory(parent=self.module, slug='some-other-chapter', owner=self.admin)

        # chapter has some content blocks a, b, c
        self.title_visible = 'visible'
        self.title_hidden = 'hidden'
        self.title_custom = 'custom'
        self.title_custom_hidden = 'custom-hidden'
        self.visible_content_block = ContentBlockFactory(parent=self.chapter, module=self.module,
                                                         title=self.title_visible, slug='cb-a', owner=self.admin)
        self.hidden_content_block = ContentBlockFactory(parent=self.chapter, module=self.module,
                                                        title=self.title_hidden, slug='cb-b', owner=self.admin)
        # content block c is user created
        self.custom_content_block = ContentBlockFactory(parent=self.chapter, owner=self.teacher, user_created=True,
                                                        module=self.module, title=self.title_custom,
                                                        slug='cb-c')
        # content block d is user created, but hidden
        self.custom_hidden_content_block = ContentBlockFactory(parent=self.chapter, owner=self.teacher,
                                                               user_created=True,
                                                               module=self.module, title=self.title_custom_hidden,
                                                               slug='cb-d')
        # content block a and c are visible to school class X
        self.hidden_content_block.hidden_for.add(self.skillbox_class)
        self.custom_content_block.visible_for.add(self.skillbox_class)

        # chapter title is hidden for school class X
        self.chapter.title_hidden_for.add(self.skillbox_class)

        # we make a snapshot S of the module M
        # snapshot S looks like module M for school class X

        objective_group = ObjectiveGroupFactory(module=self.module)
        second_objective_group = ObjectiveGroupFactory(module=self.module)

        self.visible_objective = ObjectiveFactory(text='visible-objective', group=objective_group)
        self.hidden_objective = ObjectiveFactory(text='hidden-objective', group=objective_group)
        self.custom_objective = ObjectiveFactory(text='custom-objective', group=objective_group, owner=self.teacher)
        self.custom_hidden_objective = ObjectiveFactory(text='custom-hidden-objective', group=objective_group,
                                                        owner=self.teacher)

        self.hidden_objective.hidden_for.add(self.skillbox_class)
        self.custom_objective.visible_for.add(self.skillbox_class)
        second_objective_group.hidden_for.add(self.skillbox_class)

    def _test_module_visibility(self, client, school_class_name):
        result = client.execute(MODULE_QUERY, variables={
            'slug': self.module.slug
        })
        self.assertIsNone(result.get('errors'))
        module = result.get('data').get('module')
        chapter = module.get('chapters')[0]
        self.assertIsNotNone(chapter)
        content_blocks = chapter.get('contentBlocks')
        content_block_titles = [content_block['title'] for content_block in content_blocks]
        self.assertTrue(self.title_visible in content_block_titles)
        self.assertTrue(self.title_hidden in content_block_titles)
        self.assertTrue(self.title_custom in content_block_titles)
        hidden_content_block = [content_block for content_block in content_blocks if
                                content_block['title'] == self.title_hidden][0]
        custom_content_block = [content_block for content_block in content_blocks if
                                content_block['title'] == self.title_custom][0]
        # check if hidden content block is hidden for this school class
        self.assertTrue(
            school_class_name in [school_class['name'] for school_class in
                                  hidden_content_block.get('hiddenFor')])
        # check if the custom content block is visible for this school class
        self.assertTrue(
            school_class_name in [school_class['name'] for school_class in
                                  custom_content_block.get('visibleFor')])

        objectives = module['objectiveGroups'][0]['objectives']

        self.assertEqual(len(objectives), 4)

        hidden_objective = [objective for objective in objectives if
                            objective['text'] == self.hidden_objective.text][0]
        custom_objective = [objective for objective in objectives if
                            objective['text'] == self.custom_objective.text][0]

        # check if hidden objective is hidden for this school class
        self.assertTrue(
            school_class_name in [school_class['name'] for school_class in
                                  hidden_objective.get('hiddenFor')])
        # check if the custom objective is visible for this school class
        self.assertTrue(
            school_class_name in [school_class['name'] for school_class in
                                  custom_objective.get('visibleFor')])

        return module

    def _compare_content_blocks(self, content_blocks):
        self.assertEqual(len(content_blocks), 4)
        first, second, third, fourth = content_blocks
        self._test_content_block(first, self.title_visible, False)
        self._test_content_block(second, self.title_hidden, True)
        self._test_content_block(third, self.title_custom, False)
        self._test_content_block(fourth, self.title_custom_hidden, True)

    def test_setup(self):
        # make sure everything is setup correctly
        self._test_module_visibility(self.client, 'skillbox')

    def _test_create_snapshot(self, result, num_snapshots=1):
        self.assertIsNone(result.get('errors'))
        snapshot = result.get('data').get('createSnapshot').get('snapshot')
        chapter = snapshot.get('chapters')[0]

        self.assertIsNotNone(snapshot.get('created'))
        self.assertEqual(snapshot.get('creator'), f'{self.teacher.first_name} {self.teacher.last_name}')

        self.assertTrue(chapter['titleHidden'])
        self.assertFalse(chapter['descriptionHidden'])
        _, chapter_id = from_global_id(chapter['id'])
        self.assertEqual(int(chapter_id), self.chapter.id)

        content_blocks = chapter['contentBlocks']
        self._compare_content_blocks(content_blocks)
        self.assertEqual(ChapterSnapshot.objects.count(), 2 * num_snapshots)

        visible, hidden, custom, custom_hidden = snapshot['objectiveGroups'][0]['objectives']

        self._test_objective(objective=visible, text=self.visible_objective.text, hidden=False)
        self._test_objective(objective=hidden, text=self.hidden_objective.text, hidden=True)
        self._test_objective(objective=custom, text=self.custom_objective.text, hidden=False)
        self._test_objective(objective=custom_hidden, text=self.custom_hidden_objective.text, hidden=True)

        id = snapshot['id']
        snapshot = get_object(Snapshot, id)
        self.assertEqual(snapshot.objective_groups.count(), 2)

    def test_create_snapshot(self):
        result = self.client.execute(CREATE_SNAPSHOT_MUTATION, variables={
            'input': {
                'module': self.slug,
                'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
            }
        })
        self._test_create_snapshot(result)

    def test_create_two_snapshots(self):
        self.client.execute(CREATE_SNAPSHOT_MUTATION, variables={
            'input': {
                'module': self.slug,
                'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
            }
        })
        result = self.client.execute(CREATE_SNAPSHOT_MUTATION, variables={
            'input': {
                'module': self.slug,
                'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
            }
        })
        self._test_create_snapshot(result, num_snapshots=2)

    def test_apply_snapshot(self):
        self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class,
                                                         user=self.teacher)
        self.assertEqual(Snapshot.objects.count(), 1)
        self.assertEqual(self.snapshot.custom_objectives.count(), 2)
        result = self.get_client(self.teacher2).execute(APPLY_SNAPSHOT_MUTATION, variables={
            'input': {
                'snapshot': to_global_id('SnapshotNode', self.snapshot.pk),
                'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk),
            }
        })
        self.assertIsNone(result.get('errors'))
        module = self._test_module_visibility(self.get_client(self.teacher2), self.second_class_name)
        original_creator = module['chapters'][0]['contentBlocks'][2].get('originalCreator')
        self.assertIsNotNone(original_creator)
        self.assertEqual(original_creator.get('id'), to_global_id('PublicUserNode', self.teacher.pk))

    def test_display_snapshot_module(self):
        self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class,
                                                         user=self.teacher)
        id = to_global_id('SnapshotNode', self.snapshot.id)
        snapshot_result = self.client.execute(SNAPSHOT_MODULE_QUERY, variables={
            'id': id
        })
        self.assertIsNone(snapshot_result.get('errors'))
        snapshot = snapshot_result.get('data').get('snapshot')
        chapters = snapshot.get('chapters')
        self.assertEqual(len(chapters), 2)
        chapter = chapters[0]
        content_blocks = chapter.get('contentBlocks')

        self._compare_content_blocks(content_blocks)

        objective_groups = snapshot['objectiveGroups']
        self.assertEqual(len(objective_groups), 2)
        objective_group1, objective_group2 = objective_groups

        objective1, objective2, objective3, objective4 = objective_group1['objectives']
        self._test_objective(objective1, self.visible_objective.text, False)
        self._test_objective(objective2, self.hidden_objective.text, True)
        self._test_objective(objective3, self.custom_objective.text, False)
        self._test_objective(objective4, self.custom_hidden_objective.text, True)

        self.assertEqual(objective_group2['hidden'], True)

        changes = snapshot['changes']
        self.assertEqual(changes['newContentBlocks'], 1)
        self.assertEqual(changes['hiddenContentBlocks'], 1)
        self.assertEqual(changes['newObjectives'], 1)
        self.assertEqual(changes['hiddenObjectives'], 1)

    def test_apply_initial_snapshot(self):
        third_class = SchoolClassFactory(
            users=[self.teacher2],
            name='third_class'
        )

        # make a neutral snapshot, nothing new, nothing hidden
        result = self.get_client(user=self.teacher2).execute(CREATE_SNAPSHOT_MUTATION, variables={
            'input': {
                'module': self.slug,
                'selectedClass': to_global_id('SchoolClassNode', third_class.pk),
            }
        })
        self.assertIsNone(result.get('errors'))
        snapshot_id = result['data']['createSnapshot']['snapshot']['id']

        result = self.client.execute(APPLY_SNAPSHOT_MUTATION, variables={
            'input': {
                'snapshot': snapshot_id,
                'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
            }
        })
        self.assertIsNone(result.get('errors'))

        result = self.client.execute(MODULE_QUERY, variables={
            'slug': self.module.slug
        })
        self.assertIsNone(result.get('errors'))
        module = result['data']['module']
        chapter1, chapter2 = module['chapters']
        visible, hidden, custom, custom_hidden = chapter1['contentBlocks']
        self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in visible['hiddenFor']])
        self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in hidden['hiddenFor']])
        self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in custom['visibleFor']])
        self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in custom_hidden['visibleFor']])

    def test_create_apply_view_snapshot(self):
        result = self.graphene_client.execute(MODULE_QUERY, variables={
            'slug': self.module.slug
        })
        self.assertIsNone(result.get('errors'))
        chapter = result['data']['module']['chapters'][0]
        self.assertEqual(len(chapter['contentBlocks']), 4)

        result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={
            'input': {
                'module': self.slug,
                'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
            }
        })
        self.assertIsNone(result.get('errors'))
        snapshot_id = result['data']['createSnapshot']['snapshot']['id']

        teacher2 = User.objects.get(username='teacher2')
        teacher2_client = self.get_client(user=teacher2)
        result = teacher2_client.execute(APPLY_SNAPSHOT_MUTATION, variables={
            'input': {
                'snapshot': snapshot_id,
                'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk),
            }
        })
        self.assertIsNone(result.get('errors'))

        result = self.graphene_client.execute(MODULE_QUERY, variables={
            'slug': self.module.slug
        })
        self.assertIsNone(result.get('errors'))
        chapter = result['data']['module']['chapters'][0]
        self.assertEqual(len(chapter['contentBlocks']), 4)


    def test_snapshot_chapter_visibility_after_apply(self):
        result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={
            'input': {
                'module': self.slug,
                'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
            }
        })

        self.chapter.title_hidden_for.remove(self.skillbox_class)
        self.chapter.description_hidden_for.remove(self.skillbox_class)

        self.chapter.title_hidden_for.add(self.second_class)
        self.chapter.description_hidden_for.add(self.second_class)

        def assert_chapter_hidden(hidden):
            a_result = self.get_client(self.teacher2).execute(MODULE_QUERY, variables={
                'slug': self.module.slug
            })
            self.assertIsNone(a_result.get('errors'))
            a_chapter = a_result['data']['module']['chapters'][0]
            self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['titleHiddenFor']), hidden)
            self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['descriptionHiddenFor']), hidden)

        assert_chapter_hidden(True)

        result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={
            'input': {
                'module': self.slug,
                'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
            }
        })
        self.assertIsNone(result.get('errors'))
        snapshot = result['data']['createSnapshot']['snapshot']
        snapshot_id = snapshot['id']
        chapter = snapshot['chapters'][0]
        self.assertEqual(chapter['titleHidden'], False)
        self.assertEqual(chapter['descriptionHidden'], False)

        result = self.get_client(self.teacher2).execute(APPLY_SNAPSHOT_MUTATION, variables={
            'input': {
                'snapshot': snapshot_id,
                'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk),
            }
        })
        self.assertIsNone(result.get('errors'))

        assert_chapter_hidden(False)


class SnapshotTestCase(SkillboxTestCase):
    def setUp(self) -> None:
        self.createDefault()
        self.client = self.get_client()
        self.slug = 'some-module'

        self.teacher2 = User.objects.get(username='teacher2')
        self.module = ModuleFactory(slug=self.slug)
        self.skillbox_class = SchoolClass.objects.get(name='skillbox')
        self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class,
                                                         user=self.teacher)
        Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class,
                                         user=self.teacher2)

    def test_show_only_own_snapshots(self):
        result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={
            "slug": self.slug
        })
        self.assertIsNone(result.get('errors'))
        snapshots = result['data']['module']['snapshots']
        self.assertEqual(len(snapshots), 1)
        self.assertEqual(snapshots[0]['creator'], f'{self.teacher.first_name} {self.teacher.last_name}')

    def test_share_snapshot(self):
        self.assertFalse(self.snapshot.shared)
        result = self.client.execute(SHARE_SNAPSHOT_MUTATION, variables={
            'input': {
                'snapshot': to_global_id('Snapshot', self.snapshot.id),
                'shared': True
            }
        })
        self.assertIsNone(result.get('errors'))
        data = result['data']['shareSnapshot']
        self.assertTrue(data['success'])
        self.assertTrue(data['snapshot']['shared'])
        snapshot = Snapshot.objects.get(pk=self.snapshot.pk)
        self.assertTrue(snapshot.shared)

    def test_dont_share_foreign_snapshot(self):
        self.assertFalse(self.snapshot.shared)
        teacher2_client = self.get_client(self.teacher2)
        result = teacher2_client.execute(SHARE_SNAPSHOT_MUTATION, variables={
            'input': {
                'snapshot': to_global_id('Snapshot', self.snapshot.id),
                'shared': True
            }
        })
        self.assertIsNotNone(result.get('errors'))
