"""
Tests for the badges API views.
"""
from ddt import ddt, data, unpack
from django.conf import settings
from django.test.utils import override_settings
from nose.plugins.attrib import attr

from badges.tests.factories import BadgeAssertionFactory, BadgeClassFactory, RandomBadgeClassFactory
from openedx.core.lib.api.test_utils import ApiTestCase
from student.tests.factories import UserFactory
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory

FEATURES_WITH_BADGES_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_BADGES_ENABLED['ENABLE_OPENBADGES'] = True


@override_settings(FEATURES=FEATURES_WITH_BADGES_ENABLED)
class UserAssertionTestCase(UrlResetMixin, ModuleStoreTestCase, ApiTestCase):
    """
    Mixin for badge API tests.
    """

    def setUp(self, *args, **kwargs):
        super(UserAssertionTestCase, self).setUp(*args, **kwargs)
        self.course = CourseFactory.create()
        self.user = UserFactory.create()
        # Password defined by factory.
        self.client.login(username=self.user.username, password='test')

    def url(self):
        """
        Return the URL to look up the current user's assertions.
        """
        return '/api/badges/v1/assertions/user/{}/'.format(self.user.username)

    def check_class_structure(self, badge_class, json_class):
        """
        Check a JSON response against a known badge class.
        """
        self.assertEqual(badge_class.issuing_component, json_class['issuing_component'])
        self.assertEqual(badge_class.slug, json_class['slug'])
        self.assertIn(badge_class.image.url, json_class['image_url'])
        self.assertEqual(badge_class.description, json_class['description'])
        self.assertEqual(badge_class.criteria, json_class['criteria'])
        self.assertEqual(badge_class.course_id and unicode(badge_class.course_id), json_class['course_id'])

    def check_assertion_structure(self, assertion, json_assertion):
        """
        Check a JSON response against a known assertion object.
        """
        self.assertEqual(assertion.image_url, json_assertion['image_url'])
        self.assertEqual(assertion.assertion_url, json_assertion['assertion_url'])
        self.check_class_structure(assertion.badge_class, json_assertion['badge_class'])

    def get_course_id(self, wildcard, badge_class):
        """
        Used for tests which may need to test for a course_id or a wildcard.
        """
        if wildcard:
            return '*'
        else:
            return unicode(badge_class.course_id)

    def create_badge_class(self, check_course, **kwargs):
        """
        Create a badge class, using a course id if it's relevant to the URL pattern.
        """
        if check_course:
            return RandomBadgeClassFactory.create(course_id=self.course.location.course_key, **kwargs)
        return RandomBadgeClassFactory.create(**kwargs)

    def get_qs_args(self, check_course, wildcard, badge_class):
        """
        Get a dictionary to be serialized into querystring params based on class settings.
        """
        qs_args = {
            'issuing_component': badge_class.issuing_component,
            'slug': badge_class.slug,
        }
        if check_course:
            qs_args['course_id'] = self.get_course_id(wildcard, badge_class)
        return qs_args


class TestUserBadgeAssertions(UserAssertionTestCase):
    """
    Test the general badge assertions retrieval view.
    """

    def test_get_assertions(self):
        """
        Verify we can get all of a user's badge assertions.
        """
        for dummy in range(3):
            BadgeAssertionFactory(user=self.user)
        # Add in a course scoped badge-- these should not be excluded from the full listing.
        BadgeAssertionFactory(user=self.user, badge_class=BadgeClassFactory(course_id=self.course.location.course_key))
        # Should not be included.
        for dummy in range(3):
            self.create_badge_class(False)
        response = self.get_json(self.url())
        # pylint: disable=no-member
        self.assertEqual(len(response['results']), 4)

    def test_assertion_structure(self):
        badge_class = self.create_badge_class(False)
        assertion = BadgeAssertionFactory.create(user=self.user, badge_class=badge_class)
        response = self.get_json(self.url())
        # pylint: disable=no-member
        self.check_assertion_structure(assertion, response['results'][0])


class TestUserCourseBadgeAssertions(UserAssertionTestCase):
    """
    Test the Badge Assertions view with the course_id filter.
    """

    def test_get_assertions(self):
        """
        Verify we can get assertions via the course_id and username.
        """
        course_key = self.course.location.course_key
        badge_class = BadgeClassFactory.create(course_id=course_key)
        for dummy in range(3):
            BadgeAssertionFactory.create(user=self.user, badge_class=badge_class)
        # Should not be included, as they don't share the target badge class.
        for dummy in range(3):
            BadgeAssertionFactory.create(user=self.user)
        # Also should not be included, as they don't share the same user.
        for dummy in range(6):
            BadgeAssertionFactory.create(badge_class=badge_class)
        response = self.get_json(self.url(), data={'course_id': course_key})
        # pylint: disable=no-member
        self.assertEqual(len(response['results']), 3)
        unused_course = CourseFactory.create()
        response = self.get_json(self.url(), data={'course_id': unused_course.location.course_key})
        # pylint: disable=no-member
        self.assertEqual(len(response['results']), 0)

    def test_assertion_structure(self):
        """
        Verify the badge assertion structure is as expected when a course is involved.
        """
        course_key = self.course.location.course_key
        badge_class = BadgeClassFactory.create(course_id=course_key)
        assertion = BadgeAssertionFactory.create(badge_class=badge_class, user=self.user)
        response = self.get_json(self.url())
        # pylint: disable=no-member
        self.check_assertion_structure(assertion, response['results'][0])


@attr(shard=3)
@ddt
class TestUserBadgeAssertionsByClass(UserAssertionTestCase):
    """
    Test the Badge Assertions view with the badge class filter.
    """

    @unpack
    @data((False, False), (True, False), (True, True))
    def test_get_assertions(self, check_course, wildcard):
        """
        Verify we can get assertions via the badge class and username.
        """
        badge_class = self.create_badge_class(check_course)
        for dummy in range(3):
            BadgeAssertionFactory.create(user=self.user, badge_class=badge_class)
        if badge_class.course_id:
            # Also create a version of this badge under a different course.
            alt_class = BadgeClassFactory.create(
                slug=badge_class.slug, issuing_component=badge_class.issuing_component,
                course_id=CourseFactory.create().location.course_key
            )
            BadgeAssertionFactory.create(user=self.user, badge_class=alt_class)
        # Same badge class, but different user. Should not show up in the list.
        for dummy in range(5):
            BadgeAssertionFactory.create(badge_class=badge_class)
        # Different badge class AND different user. Certainly shouldn't show up in the list!
        for dummy in range(6):
            BadgeAssertionFactory.create()

        response = self.get_json(
            self.url(),
            data=self.get_qs_args(check_course, wildcard, badge_class),
        )
        if wildcard:
            expected_length = 4
        else:
            expected_length = 3
        # pylint: disable=no-member
        self.assertEqual(len(response['results']), expected_length)
        unused_class = self.create_badge_class(check_course, slug='unused_slug', issuing_component='unused_component')

        response = self.get_json(
            self.url(),
            data=self.get_qs_args(check_course, wildcard, unused_class),
        )
        # pylint: disable=no-member
        self.assertEqual(len(response['results']), 0)

    def check_badge_class_assertion(self, check_course, wildcard, badge_class):
        """
        Given a badge class, create an assertion for the current user and fetch it, checking the structure.
        """
        assertion = BadgeAssertionFactory.create(badge_class=badge_class, user=self.user)
        response = self.get_json(
            self.url(),
            data=self.get_qs_args(check_course, wildcard, badge_class),
        )
        # pylint: disable=no-member
        self.check_assertion_structure(assertion, response['results'][0])

    @unpack
    @data((False, False), (True, False), (True, True))
    def test_assertion_structure(self, check_course, wildcard):
        self.check_badge_class_assertion(check_course, wildcard, self.create_badge_class(check_course))

    @unpack
    @data((False, False), (True, False), (True, True))
    def test_empty_issuing_component(self, check_course, wildcard):
        self.check_badge_class_assertion(
            check_course, wildcard, self.create_badge_class(check_course, issuing_component='')
        )