"""
Tests for the Badges app models.
"""
from django.core.exceptions import ValidationError
from django.core.files.images import ImageFile
from django.core.files.storage import default_storage
from django.db.utils import IntegrityError
from django.test import TestCase
from django.test.utils import override_settings
from mock import patch, Mock
from nose.plugins.attrib import attr
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase

from xmodule.modulestore.tests.factories import CourseFactory

from badges.models import (
    CourseCompleteImageConfiguration, validate_badge_image, BadgeClass, BadgeAssertion,
    CourseBadgesDisabledError
)
from badges.tests.factories import BadgeClassFactory, BadgeAssertionFactory, RandomBadgeClassFactory
from certificates.tests.test_models import TEST_DATA_ROOT
from student.tests.factories import UserFactory


def get_image(name):
    """
    Get one of the test images from the test data directory.
    """
    return ImageFile(open(TEST_DATA_ROOT / 'badges' / name + '.png'))


@attr('shard_1')
class BadgeImageConfigurationTest(TestCase):
    """
    Test the validation features of BadgeImageConfiguration.
    """

    def test_no_double_default(self):
        """
        Verify that creating two configurations as default is not permitted.
        """
        CourseCompleteImageConfiguration(mode='test', icon=get_image('good'), default=True).save()
        self.assertRaises(
            ValidationError,
            CourseCompleteImageConfiguration(mode='test2', icon=get_image('good'), default=True).full_clean
        )

    def test_runs_validator(self):
        """
        Verify that the image validator is triggered when cleaning the model.
        """
        self.assertRaises(
            ValidationError,
            CourseCompleteImageConfiguration(mode='test2', icon=get_image('unbalanced')).full_clean
        )


class DummyBackend(object):
    """
    Dummy badge backend, used for testing.
    """
    award = Mock()


class BadgeClassTest(ModuleStoreTestCase):
    """
    Test BadgeClass functionality
    """

    def setUp(self):
        super(BadgeClassTest, self).setUp()
        self.addCleanup(self.cleanup_uploads)

    def cleanup_uploads(self):
        """
        Remove all files uploaded as badges.
        """
        upload_to = BadgeClass._meta.get_field('image').upload_to  # pylint: disable=protected-access
        (_, files) = default_storage.listdir(upload_to)
        for uploaded_file in files:
            default_storage.delete(upload_to + '/' + uploaded_file)

    # Need full path to make sure class names line up.
    @override_settings(BADGING_BACKEND='lms.djangoapps.badges.tests.test_models.DummyBackend')
    def test_backend(self):
        """
        Verify the BadgeClass fetches the backend properly.
        """
        self.assertIsInstance(BadgeClass().backend, DummyBackend)

    def test_get_badge_class_preexisting(self):
        """
        Verify fetching a badge first grabs existing badges.
        """
        premade_badge_class = BadgeClassFactory.create()
        # Ignore additional parameters. This class already exists.
        badge_class = BadgeClass.get_badge_class(
            slug='test_slug', issuing_component='test_component', description='Attempted override',
            criteria='test', display_name='Testola', image_file_handle=get_image('good')
        )
        # These defaults are set on the factory.
        self.assertEqual(badge_class.criteria, 'https://example.com/syllabus')
        self.assertEqual(badge_class.display_name, 'Test Badge')
        self.assertEqual(badge_class.description, "Yay! It's a test badge.")
        # File name won't always be the same.
        self.assertEqual(badge_class.image.path, premade_badge_class.image.path)

    def test_unique_for_course(self):
        """
        Verify that the course_id is used in fetching existing badges or creating new ones.
        """
        course_key = CourseFactory.create().location.course_key
        premade_badge_class = BadgeClassFactory.create(course_id=course_key)
        badge_class = BadgeClass.get_badge_class(
            slug='test_slug', issuing_component='test_component', description='Attempted override',
            criteria='test', display_name='Testola', image_file_handle=get_image('good')
        )
        course_badge_class = BadgeClass.get_badge_class(
            slug='test_slug', issuing_component='test_component', description='Attempted override',
            criteria='test', display_name='Testola', image_file_handle=get_image('good'),
            course_id=course_key,
        )
        self.assertNotEqual(badge_class.id, course_badge_class.id)
        self.assertEqual(course_badge_class.id, premade_badge_class.id)

    def test_get_badge_class_course_disabled(self):
        """
        Verify attempting to fetch a badge class for a course which does not issue badges raises an
        exception.
        """
        course_key = CourseFactory.create(metadata={'issue_badges': False}).location.course_key
        with self.assertRaises(CourseBadgesDisabledError):
            BadgeClass.get_badge_class(
                slug='test_slug', issuing_component='test_component', description='Attempted override',
                criteria='test', display_name='Testola', image_file_handle=get_image('good'),
                course_id=course_key,
            )

    def test_get_badge_class_create(self):
        """
        Verify fetching a badge creates it if it doesn't yet exist.
        """
        badge_class = BadgeClass.get_badge_class(
            slug='new_slug', issuing_component='new_component', description='This is a test',
            criteria='https://example.com/test_criteria', display_name='Super Badge',
            image_file_handle=get_image('good')
        )
        # This should have been saved before being passed back.
        self.assertTrue(badge_class.id)
        self.assertEqual(badge_class.slug, 'new_slug')
        self.assertEqual(badge_class.issuing_component, 'new_component')
        self.assertEqual(badge_class.description, 'This is a test')
        self.assertEqual(badge_class.criteria, 'https://example.com/test_criteria')
        self.assertEqual(badge_class.display_name, 'Super Badge')
        self.assertEqual(badge_class.image.name.rsplit('/', 1)[-1], 'good.png')

    def test_get_badge_class_nocreate(self):
        """
        Test returns None if the badge class does not exist.
        """
        badge_class = BadgeClass.get_badge_class(
            slug='new_slug', issuing_component='new_component', create=False
        )
        self.assertIsNone(badge_class)
        # Run this twice to verify there wasn't a background creation of the badge.
        badge_class = BadgeClass.get_badge_class(
            slug='new_slug', issuing_component='new_component', description=None,
            criteria=None, display_name=None,
            image_file_handle=None, create=False
        )
        self.assertIsNone(badge_class)

    def test_get_badge_class_image_validate(self):
        """
        Verify handing a broken image to get_badge_class raises a validation error upon creation.
        """
        self.assertRaises(
            ValidationError,
            BadgeClass.get_badge_class,
            slug='new_slug', issuing_component='new_component', description='This is a test',
            criteria='https://example.com/test_criteria', display_name='Super Badge',
            image_file_handle=get_image('unbalanced')
        )

    def test_get_badge_class_data_validate(self):
        """
        Verify handing incomplete data for required fields when making a badge class raises an Integrity error.
        """
        self.assertRaises(
            IntegrityError,
            BadgeClass.get_badge_class,
            slug='new_slug', issuing_component='new_component',
            image_file_handle=get_image('good')
        )

    def test_get_for_user(self):
        """
        Make sure we can get an assertion for a user if there is one.
        """
        user = UserFactory.create()
        badge_class = BadgeClassFactory.create()
        self.assertFalse(badge_class.get_for_user(user))
        assertion = BadgeAssertionFactory.create(badge_class=badge_class, user=user)
        self.assertEqual(list(badge_class.get_for_user(user)), [assertion])

    @override_settings(BADGING_BACKEND='lms.djangoapps.badges.backends.badgr.BadgrBackend', BADGR_API_TOKEN='test')
    @patch('lms.djangoapps.badges.backends.badgr.BadgrBackend.award')
    def test_award(self, mock_award):
        """
        Verify that the award command calls the award function on the backend with the right parameters.
        """
        user = UserFactory.create()
        badge_class = BadgeClassFactory.create()
        badge_class.award(user, evidence_url='http://example.com/evidence')
        self.assertTrue(mock_award.called)
        mock_award.assert_called_with(badge_class, user, evidence_url='http://example.com/evidence')

    def test_runs_validator(self):
        """
        Verify that the image validator is triggered when cleaning the model.
        """
        self.assertRaises(
            ValidationError,
            BadgeClass(
                slug='test', issuing_component='test2', criteria='test3',
                description='test4', image=get_image('unbalanced')
            ).full_clean
        )


class BadgeAssertionTest(ModuleStoreTestCase):
    """
    Tests for the BadgeAssertion model
    """
    def test_assertions_for_user(self):
        """
        Verify that grabbing all assertions for a user behaves as expected.

        This function uses object IDs because for some reason Jenkins trips up
        on its assertItemsEqual check here despite the items being equal.
        """
        user = UserFactory()
        assertions = [BadgeAssertionFactory.create(user=user).id for _i in range(3)]
        course = CourseFactory.create()
        course_key = course.location.course_key
        course_badges = [RandomBadgeClassFactory(course_id=course_key) for _i in range(3)]
        course_assertions = [
            BadgeAssertionFactory.create(user=user, badge_class=badge_class).id for badge_class in course_badges
        ]
        assertions.extend(course_assertions)
        assertions.sort()
        assertions_for_user = [badge.id for badge in BadgeAssertion.assertions_for_user(user)]
        assertions_for_user.sort()
        self.assertEqual(assertions_for_user, assertions)
        course_scoped_assertions = [
            badge.id for badge in BadgeAssertion.assertions_for_user(user, course_id=course_key)
        ]
        course_scoped_assertions.sort()
        self.assertEqual(course_scoped_assertions, course_assertions)


class ValidBadgeImageTest(TestCase):
    """
    Tests the badge image field validator.
    """
    def test_good_image(self):
        """
        Verify that saving a valid badge image is no problem.
        """
        validate_badge_image(get_image('good'))

    def test_unbalanced_image(self):
        """
        Verify that setting an image with an uneven width and height raises an error.
        """
        unbalanced = ImageFile(get_image('unbalanced'))
        self.assertRaises(ValidationError, validate_badge_image, unbalanced)

    def test_large_image(self):
        """
        Verify that setting an image that is too big raises an error.
        """
        large = get_image('large')
        self.assertRaises(ValidationError, validate_badge_image, large)
