"""
test utils
"""
from nose.plugins.attrib import attr

from ccx.models import (  # pylint: disable=import-error
    CcxMembership,
    CcxFutureMembership,
)
from ccx.tests.factories import (  # pylint: disable=import-error
    CcxFactory,
    CcxMembershipFactory,
    CcxFutureMembershipFactory,
)
from student.roles import CourseCcxCoachRole  # pylint: disable=import-error
from student.tests.factories import (  # pylint: disable=import-error
    AdminFactory,
    UserFactory,
)
from xmodule.modulestore.tests.django_utils import (
    ModuleStoreTestCase,
    TEST_DATA_SPLIT_MODULESTORE)
from xmodule.modulestore.tests.factories import CourseFactory
from ccx_keys.locator import CCXLocator


@attr('shard_1')
class TestEmailEnrollmentState(ModuleStoreTestCase):
    """unit tests for the EmailEnrollmentState class
    """

    def setUp(self):
        """
        Set up tests
        """
        super(TestEmailEnrollmentState, self).setUp()
        # remove user provided by the parent test case so we can make our own
        # when needed.
        self.user = None
        course = CourseFactory.create()
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=course.id, coach=coach)

    def create_user(self):
        """provide a legitimate django user for testing
        """
        if getattr(self, 'user', None) is None:
            self.user = UserFactory()

    def register_user_in_ccx(self):
        """create registration of self.user in self.ccx

        registration will be inactive
        """
        self.create_user()
        CcxMembershipFactory(ccx=self.ccx, student=self.user)

    def create_one(self, email=None):
        """Create a single EmailEnrollmentState object and return it
        """
        from ccx.utils import EmailEnrollmentState  # pylint: disable=import-error
        if email is None:
            email = self.user.email
        return EmailEnrollmentState(self.ccx, email)

    def test_enrollment_state_for_non_user(self):
        """verify behavior for non-user email address
        """
        ee_state = self.create_one(email='nobody@nowhere.com')
        for attribute in ['user', 'member', 'full_name', 'in_ccx']:
            value = getattr(ee_state, attribute, 'missing attribute')
            self.assertFalse(value, "{}: {}".format(value, attribute))

    def test_enrollment_state_for_non_member_user(self):
        """verify behavior for email address of user who is not a ccx memeber
        """
        self.create_user()
        ee_state = self.create_one()
        self.assertTrue(ee_state.user)
        self.assertFalse(ee_state.in_ccx)
        self.assertEqual(ee_state.member, self.user)
        self.assertEqual(ee_state.full_name, self.user.profile.name)

    def test_enrollment_state_for_member_user(self):
        """verify behavior for email address of user who is a ccx member
        """
        self.create_user()
        self.register_user_in_ccx()
        ee_state = self.create_one()
        for attribute in ['user', 'in_ccx']:
            self.assertTrue(
                getattr(ee_state, attribute, False),
                "attribute {} is missing or False".format(attribute)
            )
        self.assertEqual(ee_state.member, self.user)
        self.assertEqual(ee_state.full_name, self.user.profile.name)

    def test_enrollment_state_to_dict(self):
        """verify dict representation of EmailEnrollmentState
        """
        self.create_user()
        self.register_user_in_ccx()
        ee_state = self.create_one()
        ee_dict = ee_state.to_dict()
        expected = {
            'user': True,
            'member': self.user,
            'in_ccx': True,
        }
        for expected_key, expected_value in expected.iteritems():
            self.assertTrue(expected_key in ee_dict)
            self.assertEqual(expected_value, ee_dict[expected_key])

    def test_enrollment_state_repr(self):
        self.create_user()
        self.register_user_in_ccx()
        ee_state = self.create_one()
        representation = repr(ee_state)
        self.assertTrue('user=True' in representation)
        self.assertTrue('in_ccx=True' in representation)
        member = 'member={}'.format(self.user)
        self.assertTrue(member in representation)


@attr('shard_1')
# TODO: deal with changes in behavior for auto_enroll
class TestGetEmailParams(ModuleStoreTestCase):
    """tests for ccx.utils.get_email_params
    """
    MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

    def setUp(self):
        """
        Set up tests
        """
        super(TestGetEmailParams, self).setUp()
        course = CourseFactory.create()
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=course.id, coach=coach)
        self.all_keys = [
            'site_name', 'course', 'course_url', 'registration_url',
            'course_about_url', 'auto_enroll'
        ]
        self.url_keys = [k for k in self.all_keys if 'url' in k]
        self.course_keys = [k for k in self.url_keys if 'course' in k]

    def call_fut(self, auto_enroll=False, secure=False):
        """
        call function under test
        """
        from ccx.utils import get_email_params  # pylint: disable=import-error
        return get_email_params(self.ccx, auto_enroll, secure)

    def test_params_have_expected_keys(self):
        params = self.call_fut()
        self.assertFalse(set(params.keys()) - set(self.all_keys))

    def test_ccx_id_in_params(self):
        expected_course_id = unicode(CCXLocator.from_course_locator(self.ccx.course_id, self.ccx.id))
        params = self.call_fut()
        self.assertEqual(params['course'], self.ccx)
        for url_key in self.url_keys:
            self.assertTrue('http://' in params[url_key])
        for url_key in self.course_keys:
            self.assertTrue(expected_course_id in params[url_key])

    def test_security_respected(self):
        secure = self.call_fut(secure=True)
        for url_key in self.url_keys:
            self.assertTrue('https://' in secure[url_key])
        insecure = self.call_fut(secure=False)
        for url_key in self.url_keys:
            self.assertTrue('http://' in insecure[url_key])

    def test_auto_enroll_passed_correctly(self):
        not_auto = self.call_fut(auto_enroll=False)
        self.assertFalse(not_auto['auto_enroll'])
        auto = self.call_fut(auto_enroll=True)
        self.assertTrue(auto['auto_enroll'])


@attr('shard_1')
# TODO: deal with changes in behavior for auto_enroll
class TestEnrollEmail(ModuleStoreTestCase):
    """tests for the enroll_email function from ccx.utils
    """
    MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

    def setUp(self):
        super(TestEnrollEmail, self).setUp()
        # unbind the user created by the parent, so we can create our own when
        # needed.
        self.user = None
        course = CourseFactory.create()
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=course.id, coach=coach)
        self.outbox = self.get_outbox()

    def create_user(self):
        """provide a legitimate django user for testing
        """
        if getattr(self, 'user', None) is None:
            self.user = UserFactory()

    def register_user_in_ccx(self):
        """create registration of self.user in self.ccx

        registration will be inactive
        """
        self.create_user()
        CcxMembershipFactory(ccx=self.ccx, student=self.user)

    def get_outbox(self):
        """Return the django mail outbox"""
        from django.core import mail
        return mail.outbox

    def check_membership(self, email=None, user=None, future=False):
        """Verify tjat an appropriate CCX Membership exists"""
        if not email and not user:
            self.fail(
                "must provide user or email address to check CCX Membership"
            )
        if future and email:
            membership = CcxFutureMembership.objects.filter(
                ccx=self.ccx, email=email
            )
        elif not future:
            if not user:
                user = self.user
            membership = CcxMembership.objects.filter(
                ccx=self.ccx, student=user
            )
        self.assertTrue(membership.exists())

    def check_enrollment_state(self, state, in_ccx, member, user):
        """Verify an enrollment state object against provided arguments

        state.in_ccx will always be a boolean
        state.user will always be a boolean
        state.member will be a Django user object or None
        """
        self.assertEqual(in_ccx, state.in_ccx)
        self.assertEqual(member, state.member)
        self.assertEqual(user, state.user)

    def call_fut(
            self,
            student_email=None,
            auto_enroll=False,
            email_students=False,
            email_params=None
    ):
        """Call function under test"""
        from ccx.utils import enroll_email  # pylint: disable=import-error
        if student_email is None:
            student_email = self.user.email
        before, after = enroll_email(
            self.ccx, student_email, auto_enroll, email_students, email_params
        )
        return before, after

    def test_enroll_non_user_sending_email(self):
        """enroll a non-user email and send an enrollment email to them
        """
        # ensure no emails are in the outbox now
        self.assertEqual(self.outbox, [])
        test_email = "nobody@nowhere.com"
        before, after = self.call_fut(
            student_email=test_email, email_students=True
        )

        # there should be a future membership set for this email address now
        self.check_membership(email=test_email, future=True)
        for state in [before, after]:
            self.check_enrollment_state(state, False, None, False)
        # mail was sent and to the right person
        self.assertEqual(len(self.outbox), 1)
        msg = self.outbox[0]
        self.assertTrue(test_email in msg.recipients())

    def test_enroll_non_member_sending_email(self):
        """register a non-member and send an enrollment email to them
        """
        self.create_user()
        # ensure no emails are in the outbox now
        self.assertEqual(self.outbox, [])
        before, after = self.call_fut(email_students=True)

        # there should be a membership set for this email address now
        self.check_membership(email=self.user.email)
        self.check_enrollment_state(before, False, self.user, True)
        self.check_enrollment_state(after, True, self.user, True)
        # mail was sent and to the right person
        self.assertEqual(len(self.outbox), 1)
        msg = self.outbox[0]
        self.assertTrue(self.user.email in msg.recipients())

    def test_enroll_member_sending_email(self):
        """register a member and send an enrollment email to them
        """
        self.register_user_in_ccx()
        # ensure no emails are in the outbox now
        self.assertEqual(self.outbox, [])
        before, after = self.call_fut(email_students=True)

        # there should be a membership set for this email address now
        self.check_membership(email=self.user.email)
        for state in [before, after]:
            self.check_enrollment_state(state, True, self.user, True)
        # mail was sent and to the right person
        self.assertEqual(len(self.outbox), 1)
        msg = self.outbox[0]
        self.assertTrue(self.user.email in msg.recipients())

    def test_enroll_non_user_no_email(self):
        """register a non-user via email address but send no email
        """
        # ensure no emails are in the outbox now
        self.assertEqual(self.outbox, [])
        test_email = "nobody@nowhere.com"
        before, after = self.call_fut(
            student_email=test_email, email_students=False
        )

        # there should be a future membership set for this email address now
        self.check_membership(email=test_email, future=True)
        for state in [before, after]:
            self.check_enrollment_state(state, False, None, False)
        # ensure there are still no emails in the outbox now
        self.assertEqual(self.outbox, [])

    def test_enroll_non_member_no_email(self):
        """register a non-member but send no email"""
        self.create_user()
        # ensure no emails are in the outbox now
        self.assertEqual(self.outbox, [])
        before, after = self.call_fut(email_students=False)

        # there should be a membership set for this email address now
        self.check_membership(email=self.user.email)
        self.check_enrollment_state(before, False, self.user, True)
        self.check_enrollment_state(after, True, self.user, True)
        # ensure there are still no emails in the outbox now
        self.assertEqual(self.outbox, [])

    def test_enroll_member_no_email(self):
        """enroll a member but send no email
        """
        self.register_user_in_ccx()
        # ensure no emails are in the outbox now
        self.assertEqual(self.outbox, [])
        before, after = self.call_fut(email_students=False)

        # there should be a membership set for this email address now
        self.check_membership(email=self.user.email)
        for state in [before, after]:
            self.check_enrollment_state(state, True, self.user, True)
        # ensure there are still no emails in the outbox now
        self.assertEqual(self.outbox, [])


@attr('shard_1')
# TODO: deal with changes in behavior for auto_enroll
class TestUnenrollEmail(ModuleStoreTestCase):
    """Tests for the unenroll_email function from ccx.utils"""
    MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

    def setUp(self):
        super(TestUnenrollEmail, self).setUp()
        # unbind the user created by the parent, so we can create our own when
        # needed.
        self.user = None
        course = CourseFactory.create()
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=course.id, coach=coach)
        self.outbox = self.get_outbox()
        self.email = "nobody@nowhere.com"

    def get_outbox(self):
        """Return the django mail outbox"""
        from django.core import mail
        return mail.outbox

    def create_user(self):
        """provide a legitimate django user for testing
        """
        if getattr(self, 'user', None) is None:
            self.user = UserFactory()

    def make_ccx_membership(self):
        """create registration of self.user in self.ccx

        registration will be inactive
        """
        self.create_user()
        CcxMembershipFactory.create(ccx=self.ccx, student=self.user)

    def make_ccx_future_membership(self):
        """create future registration for email in self.ccx"""
        CcxFutureMembershipFactory.create(
            ccx=self.ccx, email=self.email
        )

    def check_enrollment_state(self, state, in_ccx, member, user):
        """Verify an enrollment state object against provided arguments

        state.in_ccx will always be a boolean
        state.user will always be a boolean
        state.member will be a Django user object or None
        """
        self.assertEqual(in_ccx, state.in_ccx)
        self.assertEqual(member, state.member)
        self.assertEqual(user, state.user)

    def check_membership(self, future=False):
        """
        check membership
        """
        if future:
            membership = CcxFutureMembership.objects.filter(
                ccx=self.ccx, email=self.email
            )
        else:
            membership = CcxMembership.objects.filter(
                ccx=self.ccx, student=self.user
            )
        return membership.exists()

    def call_fut(self, email_students=False):
        """call function under test"""
        from ccx.utils import unenroll_email  # pylint: disable=import-error
        email = getattr(self, 'user', None) and self.user.email or self.email
        return unenroll_email(self.ccx, email, email_students=email_students)

    def test_unenroll_future_member_with_email(self):
        """unenroll a future member and send an email
        """
        self.make_ccx_future_membership()
        # assert that a membership exists and that no emails have been sent
        self.assertTrue(self.check_membership(future=True))
        self.assertEqual(self.outbox, [])
        # unenroll the student
        before, after = self.call_fut(email_students=True)

        # assert that membership is now gone
        self.assertFalse(self.check_membership(future=True))
        # validate the before and after enrollment states
        for state in [before, after]:
            self.check_enrollment_state(state, False, None, False)
        # check that mail was sent and to the right person
        self.assertEqual(len(self.outbox), 1)
        msg = self.outbox[0]
        self.assertTrue(self.email in msg.recipients())

    def test_unenroll_member_with_email(self):
        """unenroll a current member and send an email"""
        self.make_ccx_membership()
        # assert that a membership exists and that no emails have been sent
        self.assertTrue(self.check_membership())
        self.assertEqual(self.outbox, [])
        # unenroll the student
        before, after = self.call_fut(email_students=True)

        # assert that membership is now gone
        self.assertFalse(self.check_membership())
        # validate the before and after enrollment state
        self.check_enrollment_state(after, False, self.user, True)
        self.check_enrollment_state(before, True, self.user, True)
        # check that mail was sent and to the right person
        self.assertEqual(len(self.outbox), 1)
        msg = self.outbox[0]
        self.assertTrue(self.user.email in msg.recipients())

    def test_unenroll_future_member_no_email(self):
        """unenroll a future member but send no email
        """
        self.make_ccx_future_membership()
        # assert that a membership exists and that no emails have been sent
        self.assertTrue(self.check_membership(future=True))
        self.assertEqual(self.outbox, [])
        # unenroll the student
        before, after = self.call_fut()

        # assert that membership is now gone
        self.assertFalse(self.check_membership(future=True))
        # validate the before and after enrollment states
        for state in [before, after]:
            self.check_enrollment_state(state, False, None, False)
        # no email was sent to the student
        self.assertEqual(self.outbox, [])

    def test_unenroll_member_no_email(self):
        """unenroll a current member but send no email
        """
        self.make_ccx_membership()
        # assert that a membership exists and that no emails have been sent
        self.assertTrue(self.check_membership())
        self.assertEqual(self.outbox, [])
        # unenroll the student
        before, after = self.call_fut()

        # assert that membership is now gone
        self.assertFalse(self.check_membership())
        # validate the before and after enrollment state
        self.check_enrollment_state(after, False, self.user, True)
        self.check_enrollment_state(before, True, self.user, True)
        # no email was sent to the student
        self.assertEqual(self.outbox, [])


@attr('shard_1')
class TestGetMembershipTriplets(ModuleStoreTestCase):
    """Verify that get_ccx_membership_triplets functions properly"""
    MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

    def setUp(self):
        """Set up a course, coach, ccx and user"""
        super(TestGetMembershipTriplets, self).setUp()
        self.course = CourseFactory.create()
        coach = AdminFactory.create()
        role = CourseCcxCoachRole(self.course.id)
        role.add_users(coach)
        self.ccx = CcxFactory(course_id=self.course.id, coach=coach)

    def make_ccx_membership(self, active=True):
        """create registration of self.user in self.ccx

        registration will be inactive
        """
        CcxMembershipFactory.create(ccx=self.ccx, student=self.user, active=active)

    def call_fut(self, org_filter=None, org_filter_out=()):
        """call the function under test in this test case"""
        from ccx.utils import get_ccx_membership_triplets
        return list(
            get_ccx_membership_triplets(self.user, org_filter, org_filter_out)
        )

    def test_no_membership(self):
        """verify that no triplets are returned if there are no memberships
        """
        triplets = self.call_fut()
        self.assertEqual(len(triplets), 0)

    def test_has_membership(self):
        """verify that a triplet is returned when a membership exists
        """
        self.make_ccx_membership()
        triplets = self.call_fut()
        self.assertEqual(len(triplets), 1)
        ccx, membership, course_overview = triplets[0]
        self.assertEqual(ccx.id, self.ccx.id)
        self.assertEqual(unicode(course_overview.id), unicode(self.course.id))
        self.assertEqual(membership.student, self.user)

    def test_has_membership_org_filtered(self):
        """verify that microsite org filter prevents seeing microsite ccx"""
        self.make_ccx_membership()
        bad_org = self.course.location.org + 'foo'
        triplets = self.call_fut(org_filter=bad_org)
        self.assertEqual(len(triplets), 0)

    def test_has_membership_org_filtered_out(self):
        """verify that microsite ccxs not seen in non-microsite view"""
        self.make_ccx_membership()
        filter_list = [self.course.location.org]
        triplets = self.call_fut(org_filter_out=filter_list)
        self.assertEqual(len(triplets), 0)
