# -*- coding: utf-8 -*-

from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.core import mail
from django.utils.translation import gettext as _
from django.test.utils import override_settings
from django.urls import NoReverseMatch

from spirit.core.tests import utils
from ..forms import RegistrationForm, ResendActivationForm, LoginForm
from ..backends import EmailAuthBackend, UsernameAuthBackend
from ...utils.tokens import UserActivationTokenGenerator
from ...models import UserProfile
from .urls import CustomRegisterForm

User = get_user_model()


class UserViewTest(TestCase):

    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.user2 = utils.create_user()
        self.category = utils.create_category()
        self.topic = utils.create_topic(self.category, user=self.user2)
        self.topic2 = utils.create_topic(self.category)

    def test_login_email(self):
        """
        try to login by email
        """
        # get
        response = self.client.get(reverse('spirit:user:auth:login'))
        self.assertEqual(response.status_code, 200)

        # post
        form_data = {'username': self.user.email, 'password': "bar"}
        response = self.client.post(reverse('spirit:user:auth:login'),
                                    form_data)
        expected_url = reverse('spirit:user:update')
        self.assertRedirects(response, expected_url, status_code=302)

    def test_login_redirect(self):
        """
        try to login with a logged in user
        """
        utils.login(self)
        response = self.client.get(reverse('spirit:user:auth:login'))
        expected_url = self.user.st.get_absolute_url()
        self.assertRedirects(response, expected_url, status_code=302)
        # next
        response = self.client.get(reverse('spirit:user:auth:login') + '?next=/fakepath/')
        self.assertRedirects(response, '/fakepath/', status_code=302, target_status_code=404)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=True)
    def test_login_email_case_insensitive(self):
        """
        try to login by email
        """
        self.assertNotEqual(
            self.user.email, self.user.email.upper())
        form_data = {
            'username': self.user.email.upper(),
            'password': "bar"}
        response = self.client.post(
            reverse('spirit:user:auth:login'), form_data)
        expected_url = reverse('spirit:user:update')
        self.assertRedirects(response, expected_url, status_code=302)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=False)
    def test_login_email_case_insensitive_off(self):
        """
        try to login by email
        """
        self.assertNotEqual(
            self.user.email, self.user.email.upper())
        form_data = {
            'username': self.user.email.upper(),
            'password': "bar"}
        response = self.client.post(
            reverse('spirit:user:auth:login'), form_data)
        self.assertEqual(response.status_code, 200)

    @override_settings(ST_CASE_INSENSITIVE_USERNAMES=True)
    def test_login_username_case_insensitive(self):
        self.assertNotEqual(
            self.user.username, self.user.username.upper())
        form_data = {
            'username': self.user.username.upper(),
            'password': "bar"}
        response = self.client.post(
            reverse('spirit:user:auth:login'), form_data)
        expected_url = reverse('spirit:user:update')
        self.assertRedirects(response, expected_url, status_code=302)

    @override_settings(ST_CASE_INSENSITIVE_USERNAMES=False)
    def test_login_username_case_insensitive_off(self):
        self.assertNotEqual(
            self.user.username, self.user.username.upper())
        form_data = {
            'username': self.user.username.upper(),
            'password': "bar"}
        response = self.client.post(
            reverse('spirit:user:auth:login'), form_data)
        self.assertEqual(response.status_code, 200)

    def test_register(self):
        """
        register
        """
        # get
        response = self.client.get(reverse('spirit:user:auth:register'))
        self.assertEqual(response.status_code, 200)

        # post
        form_data = {'username': 'uniquefoo', 'email': 'some@some.com',
                     'email2': 'some@some.com', 'password': 'pass'}
        response = self.client.post(reverse('spirit:user:auth:register'),
                                    form_data)
        expected_url = reverse('spirit:user:auth:login')
        self.assertRedirects(response, expected_url, status_code=302)

        # redirect logged in user
        utils.login(self)
        response = self.client.get(reverse('spirit:user:auth:register'))
        self.assertRedirects(response, reverse('spirit:user:update'), status_code=302)

    @override_settings(ST_CASE_INSENSITIVE_USERNAMES=True)
    def test_register_username_case_insensitive(self):
        form_data = {
            'username': 'UnIqUeFoO',
            'email': 'some@some.com',
            'email2': 'some@some.com',
            'password': 'pass'}
        response = self.client.post(
            reverse('spirit:user:auth:register'), form_data)
        expected_url = reverse('spirit:user:auth:login')
        self.assertRedirects(response, expected_url, status_code=302)
        self.assertTrue(
            User.objects.filter(username='uniquefoo').exists())
        self.assertFalse(
            User.objects.filter(username='UnIqUeFoO').exists())

    @override_settings(ST_CASE_INSENSITIVE_USERNAMES=False)
    def test_register_username_case_insensitive_off(self):
        form_data = {
            'username': 'UnIqUeFoO',
            'email': 'some@some.com',
            'email2': 'some@some.com',
            'password': 'pass'}
        response = self.client.post(
            reverse('spirit:user:auth:register'), form_data)
        expected_url = reverse('spirit:user:auth:login')
        self.assertRedirects(response, expected_url, status_code=302)
        self.assertFalse(
            User.objects.filter(username='uniquefoo').exists())
        self.assertTrue(
            User.objects.filter(username='UnIqUeFoO').exists())

    @utils.immediate_on_commit
    def test_register_email_sent(self):
        """
        register and send activation email
        """
        form_data = {'username': 'uniquefoo', 'email': 'some@some.com',
                     'email2': 'some@some.com', 'password': 'pass'}
        response = self.client.post(reverse('spirit:user:auth:register'), form_data)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, _("User activation"))

    def test_register_next_logged_in(self):
        """
        redirect next on register
        """
        # redirect logged in user
        utils.login(self)
        response = self.client.get(reverse('spirit:user:auth:register') + "?next=/fakepath/")
        self.assertRedirects(response, '/fakepath/', status_code=302, target_status_code=404)

    @override_settings(ROOT_URLCONF='spirit.user.auth.tests.urls')
    def test_register_custom_form(self):
        """
        Should allow a custom form
        """
        response = self.client.get(reverse('spirit:user:auth:register'))
        self.assertIsInstance(response.context['form'], CustomRegisterForm)

        response = self.client.post(reverse('spirit:user:auth:register'), {})
        self.assertIsInstance(response.context['form'], CustomRegisterForm)

    @override_settings(ST_TESTS_RATELIMIT_NEVER_EXPIRE=True)
    def test_login_rate_limit(self):
        """
        test rate limit 5/5m
        """
        form_data = {'username': self.user.email, 'password': "badpassword"}

        for attempt in range(5):
            url = reverse('spirit:user:auth:login')
            response = self.client.post(url, form_data)
            self.assertTemplateUsed(response, 'spirit/user/auth/login.html')

        url = reverse('spirit:user:auth:login') + "?next=/path/"
        response = self.client.post(url, form_data)
        self.assertRedirects(response, url, status_code=302)

    @override_settings(ST_TESTS_RATELIMIT_NEVER_EXPIRE=True)
    def test_custom_reset_password(self):
        """
        test rate limit 5/5m
        """
        form_data = {'email': "bademail@bad.com", }

        for attempt in range(5):
            response = self.client.post(reverse('spirit:user:auth:password-reset'), form_data)
            expected_url = reverse("spirit:user:auth:password-reset-done")
            self.assertRedirects(response, expected_url, status_code=302)

        response = self.client.post(reverse('spirit:user:auth:password-reset'), form_data)
        expected_url = reverse("spirit:user:auth:password-reset")
        self.assertRedirects(response, expected_url, status_code=302)

    @utils.immediate_on_commit
    def test_custom_reset_password_email(self):
        self.assertEqual(len(mail.outbox), 0)
        form_data = {'email': self.user.email}
        response = self.client.post(
            reverse('spirit:user:auth:password-reset'), form_data)
        expected_url = reverse("spirit:user:auth:password-reset-done")
        self.assertRedirects(response, expected_url, status_code=302)
        self.assertEqual(len(mail.outbox), 1)
        self.assertIn("Password reset on", mail.outbox[0].subject)
        self.assertIn("you requested a password reset", mail.outbox[0].body)
        self.assertEqual(mail.outbox[0].to, [self.user.email])

    def test_password_reset_confirm(self):
        """
        test access
        """
        response = self.client.get(
            reverse(
                'spirit:user:auth:password-reset-confirm',
                kwargs={'uidb64': 'f-a-k-e', 'token': 'f-a-k-e'}
            )
        )
        self.assertEqual(response.status_code, 200)

    def test_admin_login(self):
        """
        Redirect to regular user login (optional)
        make sure you added:
        admin.site.login = login_required(admin.site.login)
        to urls.py (the one in your project's root)
        """
        # TODO: document that devs should be doing this.
        try:
            url = reverse('admin:login')
        except NoReverseMatch:
            return

        response = self.client.get(url)
        expected_url = reverse("spirit:user:auth:login") + "?next=" + reverse('admin:login')
        self.assertRedirects(response, expected_url, status_code=302)

    def test_registration_activation(self):
        """
        registration activation
        """
        self.user.st.is_verified = False
        self.user.is_active = False
        self.user.save()
        token = UserActivationTokenGenerator().generate(self.user)
        response = self.client.get(
            reverse(
                'spirit:user:auth:registration-activation',
                kwargs={'pk': self.user.pk, 'token': token}
            )
        )
        expected_url = reverse("spirit:user:auth:login")
        self.assertRedirects(response, expected_url, status_code=302)
        self.assertTrue(User.objects.get(pk=self.user.pk).is_active)

    def test_registration_activation_invalid(self):
        """
        Activation token should not work if user is verified
        ActiveUserMiddleware required
        """
        self.user.st.is_verified = False
        token = UserActivationTokenGenerator().generate(self.user)

        utils.login(self)
        User.objects.filter(pk=self.user.pk).update(is_active=False)
        UserProfile.objects.filter(user__pk=self.user.pk).update(is_verified=True)
        response = self.client.get(
            reverse(
                'spirit:user:auth:registration-activation',
                kwargs={'pk': self.user.pk, 'token': token}
            )
        )
        expected_url = reverse("spirit:user:auth:login")
        self.assertRedirects(response, expected_url, status_code=302)
        self.assertFalse(User.objects.get(pk=self.user.pk).is_active)

    @utils.immediate_on_commit
    def test_resend_activation_email(self):
        """
        resend_activation_email
        """
        user = utils.create_user(password="foo")

        form_data = {'email': user.email,
                     'password': "foo"}
        response = self.client.post(reverse('spirit:user:auth:resend-activation'),
                                    form_data)
        expected_url = reverse("spirit:user:auth:login")
        self.assertRedirects(response, expected_url, status_code=302)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, _("User activation"))

        # get
        response = self.client.get(reverse('spirit:user:auth:resend-activation'))
        self.assertEqual(response.status_code, 200)

    def test_resend_activation_email_invalid_previously_logged_in(self):
        """
        resend_activation_email invalid if is_verified was set
        """
        user = utils.create_user(password="foo")
        user.st.is_verified = True
        user.st.save()

        form_data = {'email': user.email,
                     'password': "foo"}
        response = self.client.post(reverse('spirit:user:auth:resend-activation'),
                                    form_data)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 0)

    def test_resend_activation_email_invalid_email(self):
        """
        resend_activation_email invalid password
        """
        utils.create_user(password="foo")

        form_data = {'email': "bad@foo.com", }
        response = self.client.post(reverse('spirit:user:auth:resend-activation'),
                                    form_data)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 0)

    def test_resend_activation_email_redirect_logged(self):
        """
        resend_activation_email redirect to profile if user is logged in
        """
        utils.login(self)
        response = self.client.get(reverse('spirit:user:auth:resend-activation'))
        expected_url = reverse("spirit:user:update")
        self.assertRedirects(response, expected_url, status_code=302)

    def test_logout(self):
        """
        should log out on POST only
        """
        utils.login(self)

        # get should display confirmation message
        response = self.client.get(reverse('spirit:user:auth:logout'))
        self.assertEqual(response.status_code, 200)
        self.assertTrue(self.client.session.items())

        # post should log out the user (clear the session)
        response = self.client.post(reverse('spirit:user:auth:logout'))
        expected_url = "/"
        self.assertRedirects(response, expected_url, status_code=302)
        self.assertFalse(self.client.session.items())

        # next
        utils.login(self)
        self.assertTrue(self.client.session.items())
        response = self.client.post(reverse('spirit:user:auth:logout') + '?next=/fakepath/')
        self.assertRedirects(response, '/fakepath/', status_code=302, target_status_code=404)
        self.assertFalse(self.client.session.items())

    def test_logout_anonymous_redirect(self):
        """
        should log out on POST only
        """
        # redirect to login if user is anonymous
        response = self.client.get(reverse('spirit:user:auth:logout'))
        expected_url = reverse("spirit:user:auth:login")
        self.assertRedirects(response, expected_url, status_code=302)

        # next if user is anonymous
        response = self.client.get(reverse('spirit:user:auth:logout') + '?next=/fakepath/')
        self.assertRedirects(response, '/fakepath/', status_code=302, target_status_code=404)


class UserFormTest(TestCase):

    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()

    def test_registration(self):
        """
        register
        """
        form_data = {'username': 'foo', 'email': 'foo@foo.com',
                     'email2': 'foo@foo.com', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), True)

    def test_registration_login(self):
        """
        Register and login
        """
        form_data = {'username': 'foo', 'email': 'foo@foo.com',
                     'email2': 'foo@foo.com', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), True)

        user = form.save()
        self.assertFalse(user.is_active)

        user.is_active = True
        user.save()
        utils.login(self, user=user, password='pass')  # Asserts if can't login

    def test_registration_email_required(self):
        """
        Registration should require the email field
        """
        form_data = {'username': 'foo',
                     'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), False)
        self.assertIn('email', form.errors)

    def test_registration_invalid(self):
        """
        invalid email and user
        """
        User.objects.create_user(username="foo", password="bar", email="foo@foo.com")
        form_data = {'username': 'foo', 'email': 'foo@foo.com',
                     'email2': 'foo@foo.com', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), False)
        self.assertNotIn('username', form.cleaned_data)
        self.assertNotIn('foo@foo.com', form.cleaned_data)

    def test_registration_honeypot(self):
        """
        registration honeypot
        """
        form_data = {'username': 'foo', 'email': 'foo@foo.com',
                     'email2': 'foo@foo.com', 'password': 'pass',
                     'honeypot': 'im a robot'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), False)
        self.assertNotIn('honeypot', form.cleaned_data)

    def test_registration_email_duplication(self):
        """
        register, don't allow email duplication
        """
        utils.create_user(email='duplicated@bar.com')
        form_data = {'username': 'foo', 'email': 'duplicated@bar.com',
                     'email2': 'duplicated@bar.com', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), False)
        self.assertNotIn('email', form.cleaned_data)

    @override_settings(ST_UNIQUE_EMAILS=False)
    def test_registration_email_duplication_allowed(self):
        """
        Duplicated email allowed
        """
        utils.create_user(email='duplicated@bar.com')
        form_data = {'username': 'foo', 'email': 'duplicated@bar.com',
                     'email2': 'duplicated@bar.com', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), True)

    def test_registration_email_confirmation(self):
        """
        Confirmation email should match email
        """
        form_data = {'username': 'foo', 'email': 'foo@bar.com',
                     'email2': 'foofoo@bar.com', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), False)
        self.assertNotIn('email2', form.cleaned_data)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=True)
    def test_registration_email_confirmation_case_insensitive(self):
        """
        Confirmation email should match email
        """
        form_data = {'username': 'foo', 'email': 'FOO@bar.com',
                     'email2': 'FOO@BAR.COM', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), True)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=False)
    def test_registration_email_confirmation_case_sensitive(self):
        """
        Confirmation email should match email
        """
        form_data = {'username': 'foo', 'email': 'FOO@bar.com',
                     'email2': 'FOO@BAR.COM', 'password': 'pass'}
        form = RegistrationForm(data=form_data)
        self.assertEqual(form.is_valid(), False)
        self.assertNotIn('email2', form.cleaned_data)

    def test_resend_activation_email(self):
        """
        resend activation
        """
        user = utils.create_user(email="newfoo@bar.com")
        form_data = {'email': 'newfoo@bar.com', }
        form = ResendActivationForm(form_data)
        self.assertTrue(form.is_valid())
        self.assertEqual(form.get_user(), user)

    def test_resend_activation_email_invalid_email(self):
        """
        resend activation invalid
        """
        form_data = {'email': 'bad@bar.com', }
        form = ResendActivationForm(form_data)
        self.assertFalse(form.is_valid())

    def test_resend_activation_email_duplication(self):
        """
        Send email to the first *not verified* user found
        """
        utils.create_user(email="duplicated@bar.com")
        user2 = utils.create_user(email="duplicated@bar.com")
        user3 = utils.create_user(email="duplicated@bar.com")
        form_data = {'email': 'duplicated@bar.com', }
        form = ResendActivationForm(form_data)
        self.assertTrue(form.is_valid())
        self.assertEqual(form.get_user(), user3)

        user3.st.is_verified = True
        user3.st.save()
        form = ResendActivationForm(form_data)
        self.assertTrue(form.is_valid())
        self.assertEqual(form.get_user(), user2)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=True)
    def test_resend_activation_email_case_insensitive(self):
        """
        Should lower the email before checking it
        """
        user = utils.create_user(email="newfoo@bar.com")
        form_data = {'email': 'NeWfOO@bAr.COM', }
        form = ResendActivationForm(form_data)
        self.assertTrue(form.is_valid())
        self.assertEqual(form.get_user(), user)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=False)
    def test_resend_activation_email_case_sensitive(self):
        """
        Should NOT lower the email before checking it
        """
        utils.create_user(email="newfoo@bar.com")
        form_data = {'email': 'NeWfOO@bAr.COM', }
        form = ResendActivationForm(form_data)
        self.assertFalse(form.is_valid())
        self.assertRaises(AttributeError, form.get_user)

    def test_login(self):
        """
        Should login the user
        """
        utils.create_user(username="foobar", password="foo")
        form_data = {'username': "foobar", 'password': "foo"}
        form = LoginForm(data=form_data)
        self.assertTrue(form.is_valid())

    def test_login_email(self):
        """
        Should login the user by email
        """
        utils.create_user(email="foobar@bar.com", password="foo")
        form_data = {'username': "foobar@bar.com", 'password': "foo"}
        form = LoginForm(data=form_data)
        self.assertTrue(form.is_valid())

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=False)
    def test_login_email_case_sensitive(self):
        """
        Should login the user by email
        """
        utils.create_user(email="foobar@bar.com", password="foo")
        form_data = {'username': "FOOBAR@bar.com", 'password': "foo"}
        form = LoginForm(data=form_data)
        self.assertFalse(form.is_valid())

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=True)
    def test_login_email_case_sensitive(self):
        """
        Should login the user by email
        """
        utils.create_user(email="foobar@bar.com", password="foo")
        form_data = {'username': "FOOBAR@bar.com", 'password': "foo"}
        form = LoginForm(data=form_data)
        self.assertTrue(form.is_valid())

    def test_login_invalid(self):
        """
        Should not login invalid user
        """
        form = LoginForm(data={})
        self.assertFalse(form.is_valid())

    def test_login_password_invalid(self):
        """
        Should not login invalid user
        """
        utils.create_user(username="foobar", password="foo")
        form_data = {'username': "foobar", 'password': "bad"}
        form = LoginForm(data=form_data)
        self.assertFalse(form.is_valid())

    def test_login_username_invalid(self):
        """
        Should not login invalid user
        """
        utils.create_user(username="foobar", password="foo")
        form_data = {'username': "bad", 'password': "foo"}
        form = LoginForm(data=form_data)
        self.assertFalse(form.is_valid())


class UserBackendTest(TestCase):

    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user(
            email="foobar@bar.com",
            password="bar")

    def test_email_auth_backend(self):
        user = EmailAuthBackend().authenticate(
            request=None, username="foobar@bar.com", password="bar")
        self.assertEqual(user, self.user)

    def test_email_auth_backend_email_duplication(self):
        """
        it should NOT authenticate when the email is not unique (current behaviour, sorry)
        """
        utils.create_user(email="duplicated@bar.com", password="foo")
        utils.create_user(email="duplicated@bar.com", password="foo2")
        user = EmailAuthBackend().authenticate(
            request=None, username="duplicated@bar.com", password="foo")
        self.assertIsNone(user)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=True)
    def test_email_auth_backend_case_insensitive(self):
        user = EmailAuthBackend().authenticate(
            request=None, username="FooBar@bAr.COM", password="bar")
        self.assertEqual(user, self.user)

    @override_settings(ST_CASE_INSENSITIVE_EMAILS=False)
    def test_email_auth_backend_case_sensitive(self):
        user = EmailAuthBackend().authenticate(
            request=None, username="FooBar@bAr.COM", password="bar")
        self.assertIsNone(user)

    @override_settings(ST_CASE_INSENSITIVE_USERNAMES=True)
    def test_username_auth_backend_case_sensitive(self):
        usr = utils.create_user(
            username="FooBar",
            password="bar")
        user = UsernameAuthBackend().authenticate(
            request=None, username="FooBar", password="bar")
        self.assertEqual(user.pk, usr.pk)
        user = UsernameAuthBackend().authenticate(
            request=None, username="foobar", password="bar")
        self.assertEqual(user.pk, usr.pk)

    @override_settings(ST_CASE_INSENSITIVE_USERNAMES=False)
    def test_username_auth_backend_case_sensitive_off(self):
        usr = utils.create_user(
            username="FooBar",
            password="bar")
        user = UsernameAuthBackend().authenticate(
            request=None, username="FooBar", password="bar")
        self.assertEqual(user.pk, usr.pk)
        user = UsernameAuthBackend().authenticate(
            request=None, username="foobar", password="bar")
        self.assertIsNone(user)
