import re
from datetime import timedelta

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import PasswordChangeForm
from django.core import mail
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone

from userena import forms
from userena import settings as userena_settings
from userena.models import UserenaSignup
from userena.utils import get_user_profile

User = get_user_model()


class UserenaViewsTests(TestCase):
    """Test the account views"""

    fixtures = ["users", "profiles"]

    def test_valid_activation(self):
        """A ``POST`` to the activation view"""
        # First, register an account.
        self.client.post(
            reverse("userena_signup"),
            data={
                "username": "alice",
                "email": "alice@example.com",
                "password1": "swordfish",
                "password2": "swordfish",
                "tos": "on",
            },
        )
        user = User.objects.get(email="alice@example.com")

        response = self.client.get(
            reverse(
                "userena_activate",
                kwargs={"activation_key": user.userena_signup.activation_key},
            )
        )
        self.assertContains(response, "Activate your account")

        response = self.client.post(
            reverse(
                "userena_activate",
                kwargs={"activation_key": user.userena_signup.activation_key},
            )
        )
        self.assertRedirects(
            response,
            reverse(
                "userena_profile_detail", kwargs={"username": user.username}
            ),
        )

        user = User.objects.get(email="alice@example.com")
        self.assertTrue(user.is_active)

    def test_activation_expired_retry(self):
        """A ``GET`` to the activation view when activation link is expired"""
        # First, register an account.
        userena_settings.USERENA_ACTIVATION_RETRY = True
        self.client.post(
            reverse("userena_signup"),
            data={
                "username": "alice",
                "email": "alice@example.com",
                "password1": "swordfish",
                "password2": "swordfish",
                "tos": "on",
            },
        )
        user = User.objects.get(email="alice@example.com")
        user.date_joined = timezone.now() - timedelta(days=30)
        user.save()
        response = self.client.post(
            reverse(
                "userena_activate",
                kwargs={"activation_key": user.userena_signup.activation_key},
            )
        )
        self.assertContains(response, "Request a new activation link")

        user = User.objects.get(email="alice@example.com")
        self.assertTrue(not user.is_active)
        userena_settings.USERENA_ACTIVATION_RETRY = False

    def test_retry_activation_ask(self):
        """Ask for a new activation link"""
        # First, register an account.
        userena_settings.USERENA_ACTIVATION_RETRY = True
        self.client.post(
            reverse("userena_signup"),
            data={
                "username": "alice",
                "email": "alice@example.com",
                "password1": "swordfish",
                "password2": "swordfish",
                "tos": "on",
            },
        )
        user = User.objects.get(email="alice@example.com")
        user.date_joined = timezone.now() - timedelta(days=30)
        user.save()
        old_key = user.userena_signup.activation_key
        response = self.client.get(
            reverse(
                "userena_activate_retry", kwargs={"activation_key": old_key}
            )
        )

        # We must reload the object from database to get the new key
        user = User.objects.get(email="alice@example.com")
        self.assertContains(response, "Account re-activation succeeded")

        self.assertNotEqual(old_key, user.userena_signup.activation_key)
        user = User.objects.get(email="alice@example.com")
        self.assertTrue(not user.is_active)

        self.assertEqual(len(mail.outbox), 2)
        self.assertEqual(mail.outbox[1].to, ["alice@example.com"])
        self.assertTrue(
            mail.outbox[1].body.find("activate your account ") > -1
        )

        response = self.client.post(
            reverse(
                "userena_activate",
                kwargs={"activation_key": user.userena_signup.activation_key},
            )
        )
        self.assertRedirects(
            response,
            reverse(
                "userena_profile_detail", kwargs={"username": user.username}
            ),
        )

        user = User.objects.get(email="alice@example.com")
        self.assertTrue(user.is_active)
        userena_settings.USERENA_ACTIVATION_RETRY = False

    def test_invalid_activation(self):
        """A ``GET`` to the activation view with a wrong ``activation_key``."""
        response = self.client.post(
            reverse("userena_activate", kwargs={"activation_key": "fake"})
        )
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/activate_fail.html")

    def test_valid_confirmation(self):
        """A ``GET`` to the verification view"""
        # First, try to change an email.
        user = User.objects.get(pk=1)
        user.userena_signup.change_email("johnie@example.com")

        response = self.client.get(
            reverse(
                "userena_email_confirm",
                kwargs={
                    "confirmation_key": user.userena_signup.email_confirmation_key
                },
            )
        )

        self.assertRedirects(
            response,
            reverse(
                "userena_email_confirm_complete",
                kwargs={"username": user.username},
            ),
        )

    def test_invalid_confirmation(self):
        """A ``GET`` to the verification view with an invalid verification key."""
        response = self.client.get(
            reverse(
                "userena_email_confirm", kwargs={"confirmation_key": "WRONG"}
            )
        )
        self.assertTemplateUsed(response, "userena/email_confirm_fail.html")

    def test_disabled_view(self):
        """A ``GET`` to the ``disabled`` view"""
        response = self.client.get(
            reverse("userena_disabled", kwargs={"username": "john"})
        )
        self.assertEqual(response.status_code, 404)

        User.objects.filter(username="john").update(is_active=False)
        response = self.client.get(
            reverse("userena_disabled", kwargs={"username": "john"})
        )
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/disabled.html")

        # Add an activation key to simulate that the account has not been
        # activated yet
        signup = UserenaSignup.objects.get(user__username="john")
        signup.activation_key = "foo"
        signup.save()

        response = self.client.get(
            reverse("userena_disabled", kwargs={"username": "john"})
        )
        self.assertRedirects(
            response,
            reverse("userena_activate_pending", kwargs={"username": "john"}),
        )

    def test_signup_view(self):
        """A ``GET`` to the ``signup`` view"""
        response = self.client.get(reverse("userena_signup"))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/signup_form.html")

        # Check that the correct form is used.
        self.assertTrue(isinstance(response.context["form"], forms.SignupForm))

        # Now check that a different form is used when
        # ``USERENA_WITHOUT_USERNAMES`` setting is set to ``True``
        userena_settings.USERENA_WITHOUT_USERNAMES = True

        response = self.client.get(reverse("userena_signup"))
        self.assertTrue(
            isinstance(response.context["form"], forms.SignupFormOnlyEmail)
        )

        # Back to default
        userena_settings.USERENA_WITHOUT_USERNAMES = False

        # Check for 403 with signups disabled
        userena_settings.USERENA_DISABLE_SIGNUP = True

        response = self.client.get(reverse("userena_signup"))
        self.assertEqual(response.status_code, 403)

        # Back to default
        userena_settings.USERENA_DISABLE_SIGNUP = False

    def test_signup_view_signout(self):
        """Check that a newly signed user shouldn't be signed in."""
        # User should be signed in
        self.assertTrue(
            self.client.login(username="john", password="blowfish")
        )
        # Post a new, valid signup
        self.client.post(
            reverse("userena_signup"),
            data={
                "username": "alice",
                "email": "alice@example.com",
                "password1": "blueberry",
                "password2": "blueberry",
                "tos": "on",
            },
        )

        # And should now be signed out
        self.assertFalse(len(self.client.session.keys()) > 0)

    def test_signup_view_success(self):
        """
        After a ``POST`` to the ``signup`` view a new user should be created,
        the user should be logged in and redirected to the signup success page.

        """
        response = self.client.post(
            reverse("userena_signup"),
            data={
                "username": "alice",
                "email": "alice@example.com",
                "password1": "blueberry",
                "password2": "blueberry",
                "tos": "on",
            },
        )

        # Check for redirect.
        self.assertRedirects(
            response,
            reverse("userena_signup_complete", kwargs={"username": "alice"}),
        )

        # Check for new user.
        self.assertEqual(
            User.objects.filter(email__iexact="alice@example.com").count(), 1
        )

    def test_signup_view_with_signin(self):
        """
        After a ``POST`` to the ``signup`` view a new user should be created,
        the user should be logged in and redirected to the signup success page.

        """
        # If activation is required, user is not logged in after signup,
        # disregarding USERENA_SIGNIN_AFTER_SIGNUP setting
        userena_settings.USERENA_SIGNIN_AFTER_SIGNUP = True
        userena_settings.USERENA_ACTIVATION_REQUIRED = True
        self.client.post(
            reverse("userena_signup"),
            data={
                "username": "alice",
                "email": "alice@example.com",
                "password1": "blueberry",
                "password2": "blueberry",
                "tos": "on",
            },
        )
        # Immediate reset to default to avoid leaks
        userena_settings.USERENA_SIGNIN_AFTER_SIGNUP = False
        userena_settings.USERENA_ACTIVATION_REQUIRED = True

        response_check = self.client.get(
            reverse("userena_profile_edit", kwargs={"username": "alice"})
        )
        self.assertEqual(response_check.status_code, 403)

        userena_settings.USERENA_SIGNIN_AFTER_SIGNUP = True
        userena_settings.USERENA_ACTIVATION_REQUIRED = False
        self.client.post(
            reverse("userena_signup"),
            data={
                "username": "johndoe",
                "email": "johndoe@example.com",
                "password1": "blueberry",
                "password2": "blueberry",
                "tos": "on",
            },
        )
        # Immediate reset to default to avoid leaks
        userena_settings.USERENA_SIGNIN_AFTER_SIGNUP = False
        userena_settings.USERENA_ACTIVATION_REQUIRED = True

        # Kind of hackish way to check if the user is logged in
        response_check = self.client.get(
            reverse("userena_profile_edit", kwargs={"username": "johndoe"})
        )
        self.assertEqual(response_check.status_code, 200)

    def test_signin_view(self):
        """A ``GET`` to the signin view should render the correct form"""
        response = self.client.get(reverse("userena_signin"))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/signin_form.html")

    def test_signin_view_remember_me_on(self):
        """
        A ``POST`` to the signin with tells it to remember the user for
        ``REMEMBER_ME_DAYS``.

        """
        self.client.post(
            reverse("userena_signin"),
            data={
                "identification": "john@example.com",
                "password": "blowfish",
                "remember_me": True,
            },
        )
        self.assertEqual(
            self.client.session.get_expiry_age(),
            userena_settings.USERENA_REMEMBER_ME_DAYS[1] * 3600 * 24,
        )

    def test_signin_view_remember_off(self):
        """
        A ``POST`` to the signin view of which the user doesn't want to be
        remembered.

        """
        self.client.post(
            reverse("userena_signin"),
            data={
                "identification": "john@example.com",
                "password": "blowfish",
            },
        )

        self.assertTrue(self.client.session.get_expire_at_browser_close())

    def test_signin_view_inactive(self):
        """A ``POST`` from a inactive user"""
        user = User.objects.get(email="john@example.com")
        user.is_active = False
        user.save()

        response = self.client.post(
            reverse("userena_signin"),
            data={
                "identification": "john@example.com",
                "password": "blowfish",
            },
        )

        self.assertRedirects(
            response,
            reverse("userena_disabled", kwargs={"username": user.username}),
        )

    def test_signin_unactivated(self):
        self.client.post(
            reverse("userena_signup"),
            data={
                "username": "alice",
                "email": "alice@example.com",
                "password1": "swordfish",
                "password2": "swordfish",
                "tos": "on",
            },
        )

        user = User.objects.get(email="alice@example.com")

        response = self.client.post(
            reverse("userena_signin"),
            data={"identification": user.email, "password": "swordfish"},
        )

        self.assertRedirects(
            response,
            reverse(
                "userena_activate_pending", kwargs={"username": user.username}
            ),
        )

    def test_activate_pending_disabled_redirect(self):
        response = self.client.get(
            reverse("userena_activate_pending", kwargs={"username": "john"})
        )
        self.assertEqual(response.status_code, 404)

        User.objects.filter(username="john").update(is_active=False)
        response = self.client.get(
            reverse("userena_activate_pending", kwargs={"username": "john"})
        )
        self.assertRedirects(
            response,
            reverse("userena_disabled", kwargs={"username": "john"}),
        )

    def test_signin_view_success(self):
        """
        A valid ``POST`` to the signin view should redirect the user to it's
        own profile page if no ``next`` value is supplied. Else it should
        redirect to ``next``.

        """
        response = self.client.post(
            reverse("userena_signin"),
            data={
                "identification": "john@example.com",
                "password": "blowfish",
            },
        )

        self.assertRedirects(
            response,
            reverse("userena_profile_detail", kwargs={"username": "john"}),
        )

        # Redirect to supplied ``next`` value.
        response = self.client.post(
            reverse("userena_signin"),
            data={
                "identification": "john@example.com",
                "password": "blowfish",
                "next": "/accounts/",
            },
        )
        self.assertRedirects(response, "/accounts/")

    def test_signin_view_with_invalid_next(self):
        """
        If the value of "next" is not a real URL, this should not raise
        an exception
        """
        response = self.client.post(
            reverse("userena_signin"),
            data={
                "identification": "john@example.com",
                "password": "blowfish",
                "next": "something-fake",
            },
            follow=True,
        )
        self.assertEqual(response.status_code, 404)

    def test_signout_view(self):
        """A ``GET`` to the signout view"""
        response = self.client.get(reverse("userena_signout"))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/signout.html")

    def test_change_email_view(self):
        """A ``GET`` to the change e-mail view."""
        response = self.client.get(
            reverse("userena_email_change", kwargs={"username": "john"})
        )

        # Anonymous user should not be able to view the profile page
        self.assertEqual(response.status_code, 403)

        # Login
        self.client.login(username="john", password="blowfish")
        response = self.client.get(
            reverse("userena_email_change", kwargs={"username": "john"})
        )

        self.assertEqual(response.status_code, 200)

        # Check that the correct form is used.
        self.assertTrue(
            isinstance(response.context["form"], forms.ChangeEmailForm)
        )

        self.assertTemplateUsed(response, "userena/email_form.html")

    def test_change_valid_email_view(self):
        """A ``POST`` with a valid e-mail address"""
        self.client.login(username="john", password="blowfish")
        response = self.client.post(
            reverse("userena_email_change", kwargs={"username": "john"}),
            data={"email": "john_new@example.com"},
        )

        self.assertRedirects(
            response,
            reverse(
                "userena_email_change_complete", kwargs={"username": "john"}
            ),
        )

    def test_change_password_view(self):
        """A ``GET`` to the change password view"""
        self.client.login(username="john", password="blowfish")
        response = self.client.get(
            reverse("userena_password_change", kwargs={"username": "john"})
        )

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/password_form.html")
        self.assertTrue(response.context["form"], PasswordChangeForm)

    def test_change_password_view_success(self):
        """A valid ``POST`` to the password change view"""
        self.client.login(username="john", password="blowfish")

        new_password = "suckfish"
        response = self.client.post(
            reverse("userena_password_change", kwargs={"username": "john"}),
            data={
                "new_password1": new_password,
                "new_password2": "suckfish",
                "old_password": "blowfish",
            },
        )

        self.assertRedirects(
            response,
            reverse(
                "userena_password_change_complete", kwargs={"username": "john"}
            ),
        )

        # Check that the new password is set.
        john = User.objects.get(username="john")
        self.assertTrue(john.check_password(new_password))

    def test_profile_detail_view(self):
        """A ``GET`` to the detailed view of a user"""
        response = self.client.get(
            reverse("userena_profile_detail", kwargs={"username": "john"})
        )

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/profile_detail.html")

    def test_profile_edit_view(self):
        """A ``GET`` to the edit view of a users account"""
        self.client.login(username="john", password="blowfish")
        response = self.client.get(
            reverse("userena_profile_edit", kwargs={"username": "john"})
        )

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/profile_form.html")
        self.assertTrue(
            isinstance(response.context["form"], forms.EditProfileForm)
        )

    def test_profile_edit_view_success(self):
        """A ``POST`` to the edit view"""
        self.client.login(username="john", password="blowfish")
        new_about_me = "I hate it when people use my name for testing."
        response = self.client.post(
            reverse("userena_profile_edit", kwargs={"username": "john"}),
            data={
                "about_me": new_about_me,
                "privacy": "open",
                "language": "en",
            },
        )

        # A valid post should redirect to the detail page.
        self.assertRedirects(
            response,
            reverse("userena_profile_detail", kwargs={"username": "john"}),
        )

        # Users hould be changed now.
        profile = get_user_profile(user=User.objects.get(username="john"))
        self.assertEqual(profile.about_me, new_about_me)

    def test_profile_list_view(self):
        """A ``GET`` to the list view of a user"""

        # A profile list should be shown.
        userena_settings.USERENA_DISABLE_PROFILE_LIST = False
        response = self.client.get(reverse("userena_profile_list"))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "userena/profile_list.html")

        # Profile list is disabled.
        userena_settings.USERENA_DISABLE_PROFILE_LIST = True
        response = self.client.get(reverse("userena_profile_list"))
        self.assertEqual(response.status_code, 404)

    def test_password_reset_view_success(self):
        """A ``POST`` to the password reset view with email that exists"""
        response = self.client.post(
            reverse("userena_password_reset"),
            data={"email": "john@example.com"},
        )
        # check if there was success redirect to userena_password_reset_done
        # and email was sent
        self.assertEqual(response.status_code, 302)
        self.assertIn(
            reverse("userena_password_reset_done"), response.get("Location")
        )
        self.assertTrue(mail.outbox)

    def test_password_reset_view_failure(self):
        """A ``POST`` to the password reset view with incorrect email"""
        response = self.client.post(
            reverse("userena_password_reset"),
            data={"email": "no.such.user@example.com"},
        )
        # note: status code can be different depending on django version
        self.assertIn(response.status_code, [200, 302])
        self.assertFalse(mail.outbox)

    def test_password_reset_confirm(self):
        # post reset request and search form confirmation url
        self.client.post(
            reverse("userena_password_reset"),
            data={"email": "john@example.com"},
        )
        confirm_mail = mail.outbox[0]
        confirm_url = re.search(r"\bhttps?://\S+", confirm_mail.body).group()

        # get confirmation request page
        response = self.client.get(confirm_url)
        self.assertEqual(response.status_code, 302)

        confirm_url = response.url

        # post new password and check if redirected with success
        response = self.client.post(
            confirm_url,
            data={"new_password1": "pass", "new_password2": "pass"},
        )
        self.assertEqual(response.status_code, 302)
        self.assertIn(
            reverse("userena_password_reset_complete"),
            response.get("Location"),
        )
