from __future__ import (absolute_import, division, print_function,
                        unicode_literals)


from datetime import datetime, timedelta
from django.contrib.auth.models import User
from django.contrib.messages.storage.fallback import FallbackStorage
from django.core.urlresolvers import reverse
from django.core import mail
from django.test.client import Client, RequestFactory
from django.test.utils import override_settings
from django.test import TestCase
from mock import patch, call

from postorius.forms import AddressActivationForm
from postorius.models import AddressConfirmationProfile
from postorius import views
from postorius.views.user import AddressActivationView, address_activation_link


class TestAddressActivationForm(TestCase):
    """
    Test the activation form.
    """

    def test_valid_email_is_valid(self):
        data = {
            'email': 'les@example.org',
            'user_email': 'me@example.org',
        }
        form = AddressActivationForm(data)
        self.assertTrue(form.is_valid())

    def test_identical_emails_are_invalid(self):
        data = {
            'email': 'les@example.org',
            'user_email': 'les@example.org',
        }
        form = AddressActivationForm(data)
        self.assertFalse(form.is_valid())

    def test_invalid_email_is_not_valid(self):
        data = {
            'email': 'les@example',
            'user_email': 'me@example.org',
        }
        form = AddressActivationForm(data)
        self.assertFalse(form.is_valid())


class TestAddressActivationView(TestCase):
    """
    Tests to make sure the view is properly connected, renders the form
    correctly and starts the actual address activation process if a valid
    form is submitted.
    """

    def setUp(self):
        # We create a new user and log that user in.
        # We don't use Client().login because it triggers the browserid dance.
        self.user = User.objects.create_user(
            username='les', email='les@example.org', password='secret')
        self.client.post(reverse('user_login'),
                         {'username': 'les', 'password': 'secret'})

    def tearDown(self):
        # Log out and delete user.
        self.client.logout()
        self.user.delete()

    def test_view_is_connected(self):
        # The view should be connected in the url configuration.
        response = self.client.get(reverse('address_activation'))
        self.assertEqual(response.status_code, 200)

    def test_view_contains_form(self):
        # The view context should contain a form.
        response = self.client.get(reverse('address_activation'))
        self.assertTrue('form' in response.context)

    def test_view_renders_correct_template(self):
        # The view should render the user_address_activation template.
        response = self.client.get(reverse('address_activation'))
        self.assertIn('postorius/user/address_activation.html',
                      [t.name for t in response.templates])

    def test_post_invalid_form_shows_error_msg(self):
        # Entering an invalid email address should render an error message.
        response = self.client.post(reverse('address_activation'), {
                                    'email': 'invalid_email',
                                    'user_email': self.user.email})
        self.assertTrue('Enter a valid email address.' in response.content)

    @patch.object(AddressConfirmationProfile, 'send_confirmation_link')
    def test_post_valid_form_renders_success_template(
            self, mock_send_confirmation_link):
        # Entering a valid email should render the activation_sent template.
        response = self.client.post(reverse('address_activation'), {
                                    'email': 'new_address@example.org',
                                    'user_email': self.user.email})
        self.assertEqual(mock_send_confirmation_link.call_count, 1)
        self.assertIn('postorius/user/address_activation_sent.html',
                      [t.name for t in response.templates])


class TestAddressConfirmationProfile(TestCase):
    """
    Test the confirmation of an email address activation (validating token,
    expiration, Mailman API calls etc.).
    """

    def setUp(self):
        # Create a user and profile.
        self.user = User.objects.create_user(
            username=u'ler_mm', email=u'ler@mailman.mostdesirable.org',
            password=u'pwd')
        self.profile = AddressConfirmationProfile.objects.create_profile(
            u'les@example.org', self.user)
        # Create a test request object
        self.request = RequestFactory().get('/')

    def tearDown(self):
        self.profile.delete()
        self.user.delete()
        mail.outbox = []

    def test_profile_creation(self):
        # Profile is created and has all necessary properties.
        self.assertEqual(self.profile.email, u'les@example.org')
        self.assertEqual(len(self.profile.activation_key), 40)
        self.assertTrue(type(self.profile.created), datetime)

    def test_no_duplicate_profiles(self):
        # Creating a new profile returns an existing record
        # (if one exists), instead of creating a new one.
        new_profile = AddressConfirmationProfile.objects.create_profile(
            u'les@example.org',
            User.objects.create(email=u'ler@mailman.mostdesirable.org'))
        self.assertEqual(self.profile, new_profile)

    def test_unicode_representation(self):
        # Correct unicode representation?
        self.assertEqual(unicode(self.profile),
                         'Address Confirmation Profile for les@example.org')

    def test_profile_not_expired_default_setting(self):
        # A profile created less then a day ago is not expired by default.
        delta = timedelta(hours=23)
        now = datetime.now()
        self.profile.created = now - delta
        self.assertFalse(self.profile.is_expired)

    def test_profile_is_expired_default_setting(self):
        # A profile older than 1 day is expired by default.
        delta = timedelta(days=1, hours=1)
        now = datetime.now()
        self.profile.created = now - delta
        self.assertTrue(self.profile.is_expired)

    @override_settings(
        EMAIL_CONFIRMATION_EXPIRATION_DELTA=timedelta(hours=5))
    def test_profile_not_expired(self):
        # A profile older than the timedelta set in the settings is
        # expired.
        delta = timedelta(hours=6)
        now = datetime.now()
        self.profile.created = now - delta
        self.assertTrue(self.profile.is_expired)

    @override_settings(
        EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend',
        EMAIL_CONFIRMATION_FROM='mailman@mostdesirable.org')
    def test_confirmation_link(self):
        # The profile obj can send out a confirmation email.
        # set the activation key to a fixed string for testing
        self.profile.activation_key = \
            '6323fba0097781fdb887cfc37a1122ee7c8bb0b0'
        # Simulate a VirtualHost with a different name
        self.request.META["HTTP_HOST"] = "another-virtualhost"
        # Now send the email
        self.profile.send_confirmation_link(self.request)
        self.assertEqual(mail.outbox[0].to[0], u'les@example.org')
        self.assertEqual(mail.outbox[0].subject, u'Confirmation needed')
        self.assertIn(self.profile.activation_key, mail.outbox[0].body)
        self.assertIn("another-virtualhost", mail.outbox[0].body)


class TestAddressActivationLinkSuccess(TestCase):
    """
    This tests the activation link view if the key is valid and the profile is
    not expired.
    """

    def setUp(self):
        # Set up a profile with a predictable key
        self.user = User.objects.create_user(
            username='ler', email=u'ler@example.org',
            password='pwd')
        self.profile = AddressConfirmationProfile.objects.create_profile(
            u'les@example.org', self.user)
        self.profile.activation_key = \
            u'6323fba0097781fdb887cfc37a1122ee7c8bb0b0'
        self.profile.save()

    def tearDown(self):
        self.profile.delete()
        self.user.delete()

    @patch.object(views.user, '_add_address')
    def test_mailman(self, _add_address_mock):
        # An activation key pointing to a valid profile adds the address
        # to the user.
        request = RequestFactory().get(reverse(
            'address_activation_link', kwargs={
            'activation_key': '6323fba0097781fdb887cfc37a1122ee7c8bb0b0'}))
        setattr(request, 'session', 'session')
        messages = FallbackStorage(request)
        setattr(request, '_messages', messages)
        address_activation_link(
            request, '6323fba0097781fdb887cfc37a1122ee7c8bb0b0')
        expected_calls = [call(request, u'ler@example.org',
                         u'les@example.org')]
        self.assertEqual(_add_address_mock.mock_calls, expected_calls)
