# -*- coding: utf-8 -*-
"""
Tests for custom Teams Serializers.
"""
from django.core.paginator import Paginator
from django.test.client import RequestFactory

from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory

from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
from lms.djangoapps.teams.serializers import (
    BulkTeamCountTopicSerializer,
    TopicSerializer,
    MembershipSerializer,
)


class SerializerTestCase(SharedModuleStoreTestCase):
    """
    Base test class to set up a course with topics
    """
    def setUp(self):
        """
        Set up a course with a teams configuration.
        """
        super(SerializerTestCase, self).setUp()
        self.course = CourseFactory.create(
            teams_configuration={
                "max_team_size": 10,
                "topics": [{u'name': u'Tøpic', u'description': u'The bést topic!', u'id': u'0'}]
            }
        )


class MembershipSerializerTestCase(SerializerTestCase):
    """
    Tests for the membership serializer.
    """

    def setUp(self):
        super(MembershipSerializerTestCase, self).setUp()
        self.team = CourseTeamFactory.create(
            course_id=self.course.id,
            topic_id=self.course.teams_topics[0]['id']
        )
        self.user = UserFactory.create()
        CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id)
        self.team_membership = CourseTeamMembershipFactory.create(team=self.team, user=self.user)

    def test_membership_serializer_expand_user_and_team(self):
        """Verify that the serializer only expands the user and team one level."""
        data = MembershipSerializer(self.team_membership, context={
            'expand': [u'team', u'user'],
            'request': RequestFactory().get('/api/team/v0/team_membership')
        }).data
        username = self.user.username
        self.assertEqual(data['user'], {
            'url': 'http://testserver/api/user/v1/accounts/' + username,
            'username': username,
            'profile_image': {
                'image_url_full': 'http://testserver/static/default_500.png',
                'image_url_large': 'http://testserver/static/default_120.png',
                'image_url_medium': 'http://testserver/static/default_50.png',
                'image_url_small': 'http://testserver/static/default_30.png',
                'has_image': False
            },
            'account_privacy': None
        })
        self.assertNotIn('membership', data['team'])


class TopicSerializerTestCase(SerializerTestCase):
    """
    Tests for the `TopicSerializer`, which should serialize team count data for
    a single topic.
    """
    def test_topic_with_no_team_count(self):
        """
        Verifies that the `TopicSerializer` correctly displays a topic with a
        team count of 0, and that it only takes one SQL query.
        """
        with self.assertNumQueries(1):
            serializer = TopicSerializer(self.course.teams_topics[0], context={'course_id': self.course.id})
            self.assertEqual(
                serializer.data,
                {u'name': u'Tøpic', u'description': u'The bést topic!', u'id': u'0', u'team_count': 0}
            )

    def test_topic_with_team_count(self):
        """
        Verifies that the `TopicSerializer` correctly displays a topic with a
        positive team count, and that it only takes one SQL query.
        """
        CourseTeamFactory.create(course_id=self.course.id, topic_id=self.course.teams_topics[0]['id'])
        with self.assertNumQueries(1):
            serializer = TopicSerializer(self.course.teams_topics[0], context={'course_id': self.course.id})
            self.assertEqual(
                serializer.data,
                {u'name': u'Tøpic', u'description': u'The bést topic!', u'id': u'0', u'team_count': 1}
            )

    def test_scoped_within_course(self):
        """Verify that team count is scoped within a course."""
        duplicate_topic = self.course.teams_topics[0]
        second_course = CourseFactory.create(
            teams_configuration={
                "max_team_size": 10,
                "topics": [duplicate_topic]
            }
        )
        CourseTeamFactory.create(course_id=self.course.id, topic_id=duplicate_topic[u'id'])
        CourseTeamFactory.create(course_id=second_course.id, topic_id=duplicate_topic[u'id'])
        with self.assertNumQueries(1):
            serializer = TopicSerializer(self.course.teams_topics[0], context={'course_id': self.course.id})
            self.assertEqual(
                serializer.data,
                {u'name': u'Tøpic', u'description': u'The bést topic!', u'id': u'0', u'team_count': 1}
            )


class BaseTopicSerializerTestCase(SerializerTestCase):
    """
    Base class for testing the two paginated topic serializers.
    """
    __test__ = False
    PAGE_SIZE = 5
    # Extending test classes should specify their serializer class.
    serializer = None

    def _merge_dicts(self, first, second):
        """Convenience method to merge two dicts in a single expression"""
        result = first.copy()
        result.update(second)
        return result

    def setup_topics(self, num_topics=5, teams_per_topic=0):
        """
        Helper method to set up topics on the course.  Returns a list of
        created topics.
        """
        self.course.teams_configuration['topics'] = []
        topics = [
            {u'name': u'Tøpic {}'.format(i), u'description': u'The bést topic! {}'.format(i), u'id': unicode(i)}
            for i in xrange(num_topics)
        ]
        for i in xrange(num_topics):
            topic_id = unicode(i)
            self.course.teams_configuration['topics'].append(topics[i])
            for _ in xrange(teams_per_topic):
                CourseTeamFactory.create(course_id=self.course.id, topic_id=topic_id)
        return topics

    def assert_serializer_output(self, topics, num_teams_per_topic, num_queries):
        """
        Verify that the serializer produced the expected topics.
        """
        with self.assertNumQueries(num_queries):
            page = Paginator(self.course.teams_topics, self.PAGE_SIZE).page(1)
            # pylint: disable=not-callable
            serializer = self.serializer(instance=page, context={'course_id': self.course.id})
            self.assertEqual(
                serializer.data['results'],
                [self._merge_dicts(topic, {u'team_count': num_teams_per_topic}) for topic in topics]
            )

    def test_no_topics(self):
        """
        Verify that we return no results and make no SQL queries for a page
        with no topics.
        """
        self.course.teams_configuration['topics'] = []
        self.assert_serializer_output([], num_teams_per_topic=0, num_queries=0)


class BulkTeamCountTopicSerializerTestCase(BaseTopicSerializerTestCase):
    """
    Tests for the `BulkTeamCountTopicSerializer`, which should serialize team_count
    data for many topics with constant time SQL queries.
    """
    __test__ = True
    serializer = BulkTeamCountTopicSerializer

    NUM_TOPICS = 6

    def test_topics_with_no_team_counts(self):
        """
        Verify that we serialize topics with no team count, making only one SQL
        query.
        """
        topics = self.setup_topics(teams_per_topic=0)
        self.assert_serializer_output(topics, num_teams_per_topic=0, num_queries=1)

    def test_topics_with_team_counts(self):
        """
        Verify that we serialize topics with a positive team count, making only
        one SQL query.
        """
        teams_per_topic = 10
        topics = self.setup_topics(teams_per_topic=teams_per_topic)
        self.assert_serializer_output(topics, num_teams_per_topic=teams_per_topic, num_queries=1)

    def test_subset_of_topics(self):
        """
        Verify that we serialize a subset of the course's topics, making only
        one SQL query.
        """
        teams_per_topic = 10
        topics = self.setup_topics(num_topics=self.NUM_TOPICS, teams_per_topic=teams_per_topic)
        self.assert_serializer_output(topics, num_teams_per_topic=teams_per_topic, num_queries=1)

    def test_scoped_within_course(self):
        """Verify that team counts are scoped within a course."""
        teams_per_topic = 10
        first_course_topics = self.setup_topics(num_topics=self.NUM_TOPICS, teams_per_topic=teams_per_topic)
        duplicate_topic = first_course_topics[0]
        second_course = CourseFactory.create(
            teams_configuration={
                "max_team_size": 10,
                "topics": [duplicate_topic]
            }
        )
        CourseTeamFactory.create(course_id=second_course.id, topic_id=duplicate_topic[u'id'])
        self.assert_serializer_output(first_course_topics, num_teams_per_topic=teams_per_topic, num_queries=1)

    def _merge_dicts(self, first, second):
        """Convenience method to merge two dicts in a single expression"""
        result = first.copy()
        result.update(second)
        return result

    def setup_topics(self, num_topics=5, teams_per_topic=0):
        """
        Helper method to set up topics on the course.  Returns a list of
        created topics.
        """
        self.course.teams_configuration['topics'] = []
        topics = [
            {u'name': u'Tøpic {}'.format(i), u'description': u'The bést topic! {}'.format(i), u'id': unicode(i)}
            for i in xrange(num_topics)
        ]
        for i in xrange(num_topics):
            topic_id = unicode(i)
            self.course.teams_configuration['topics'].append(topics[i])
            for _ in xrange(teams_per_topic):
                CourseTeamFactory.create(course_id=self.course.id, topic_id=topic_id)
        return topics

    def assert_serializer_output(self, topics, num_teams_per_topic, num_queries):
        """
        Verify that the serializer produced the expected topics.
        """
        with self.assertNumQueries(num_queries):
            serializer = self.serializer(topics, context={'course_id': self.course.id}, many=True)
            self.assertEqual(
                serializer.data,
                [self._merge_dicts(topic, {u'team_count': num_teams_per_topic}) for topic in topics]
            )

    def test_no_topics(self):
        """
        Verify that we return no results and make no SQL queries for a page
        with no topics.
        """
        self.course.teams_configuration['topics'] = []
        self.assert_serializer_output([], num_teams_per_topic=0, num_queries=0)
