import datetime
import hashlib
import re
from urllib.parse import urlparse, parse_qs

from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.core import mail
from django.conf import settings
from django.test import TestCase

from userena.models import UserenaSignup, upload_to_mugshot
from userena import settings as userena_settings
from userena.tests.profiles.models import Profile
from userena.utils import get_user_profile

User = get_user_model()

MUGSHOT_RE = re.compile("^[a-f0-9]{40}$")


class UserenaSignupModelTests(TestCase):
    """ Test the model of UserenaSignup """

    user_info = {
        "username": "alice",
        "password": "swordfish",
        "email": "alice@example.com",
    }

    fixtures = ["users", "profiles"]

    def test_upload_mugshot(self):
        """
        Test the uploaded path of mugshots

        TODO: What if a image get's uploaded with no extension?

        """
        user = User.objects.get(pk=1)
        filename = "my_avatar.png"
        path = upload_to_mugshot(get_user_profile(user=user), filename)

        # Path should be changed from the original
        self.assertNotEqual(filename, path)

        # Check if the correct path is returned
        MUGSHOT_RE = re.compile(
            r"^%(mugshot_path)s[\w]{10}.png$"
            % {"mugshot_path": userena_settings.USERENA_MUGSHOT_PATH}
        )

        self.assertTrue(MUGSHOT_RE.search(path))

    def test_stringification(self):
        """
        Test the stringification of a ``UserenaSignup`` object. A
        "human-readable" representation of an ``UserenaSignup`` object.

        """
        signup = UserenaSignup.objects.get(pk=1)
        self.assertEqual(signup.__str__(), signup.user.username)

    def test_change_email(self):
        """ TODO """
        pass

    def test_activation_expired_account(self):
        """
        ``UserenaSignup.activation_key_expired()`` is ``True`` when the
        ``activation_key_created`` is more days ago than defined in
        ``USERENA_ACTIVATION_DAYS``.

        """
        user = UserenaSignup.objects.create_user(**self.user_info)
        user.date_joined -= datetime.timedelta(
            days=userena_settings.USERENA_ACTIVATION_DAYS + 1
        )
        user.save()

        user = User.objects.get(username="alice")
        self.assertTrue(user.userena_signup.activation_key_expired())

    def test_activation_used_account(self):
        """
        An user cannot be activated anymore once the activation key is
        already used.

        """
        user = UserenaSignup.objects.create_user(**self.user_info)
        activated_user = UserenaSignup.objects.activate_user(
            user.userena_signup.activation_key
        )
        self.assertTrue(activated_user.userena_signup.activation_key_expired())

    def test_activation_unexpired_account(self):
        """
        ``UserenaSignup.activation_key_expired()`` is ``False`` when the
        ``activation_key_created`` is within the defined timeframe.``

        """
        user = UserenaSignup.objects.create_user(**self.user_info)
        self.assertFalse(user.userena_signup.activation_key_expired())

    def test_activation_email(self):
        """
        When a new account is created, a activation e-mail should be send out
        by ``UserenaSignup.send_activation_email``.

        """
        new_user = UserenaSignup.objects.create_user(**self.user_info)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].to, [self.user_info["email"]])

    def test_plain_email(self):
        """
        If HTML emails are disabled, check that outgoing emails are not multipart
        """
        userena_settings.USERENA_HTML_EMAIL = False
        new_user = UserenaSignup.objects.create_user(**self.user_info)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(
            str(mail.outbox[0].message()).find("multipart/alternative"), -1
        )

    def test_html_email(self):
        """
        If HTML emails are enabled, check outgoings emails are multipart and
        that different html and plain text templates are used
        """
        userena_settings.USERENA_HTML_EMAIL = True
        userena_settings.USERENA_USE_PLAIN_TEMPLATE = True

        new_user = UserenaSignup.objects.create_user(**self.user_info)

        # Reset configuration
        userena_settings.USERENA_HTML_EMAIL = False
        self.assertEqual(len(mail.outbox), 1)
        self.assertTrue(
            str(mail.outbox[0].message()).find("multipart/alternative") > -1
        )
        self.assertTrue(str(mail.outbox[0].message()).find("text/plain") > -1)
        self.assertTrue(str(mail.outbox[0].message()).find("text/html") > -1)
        self.assertTrue(str(mail.outbox[0].message()).find("<html>") > -1)
        self.assertTrue(
            str(mail.outbox[0].message()).find("<p>Thank you for signing up") > -1
        )
        self.assertFalse(mail.outbox[0].body.find("<p>Thank you for signing up") > -1)

    def test_generated_plain_email(self):
        """
        If HTML emails are enabled and plain text template are disabled,
        check outgoings emails are multipart and that plain text is generated
        from html body
        """
        userena_settings.USERENA_HTML_EMAIL = True
        userena_settings.USERENA_USE_PLAIN_TEMPLATE = False

        new_user = UserenaSignup.objects.create_user(**self.user_info)

        # Reset configuration
        userena_settings.USERENA_HTML_EMAIL = False
        userena_settings.USERENA_USE_PLAIN_TEMPLATE = True

        self.assertEqual(len(mail.outbox), 1)
        self.assertTrue(
            str(mail.outbox[0].message()).find("multipart/alternative") > -1
        )
        self.assertTrue(str(mail.outbox[0].message()).find("text/plain") > -1)
        self.assertTrue(str(mail.outbox[0].message()).find("text/html") > -1)
        self.assertTrue(str(mail.outbox[0].message()).find("<html>") > -1)
        self.assertTrue(
            str(mail.outbox[0].message()).find("<p>Thank you for signing up") > -1
        )
        self.assertTrue(mail.outbox[0].body.find("Thank you for signing up") > -1)


class BaseProfileModelTest(TestCase):
    """ Test the ``BaseProfile`` model """

    fixtures = ["users", "profiles"]

    def test_mugshot_url(self):
        """ The user has uploaded it's own mugshot. This should be returned. """
        profile = Profile.objects.get(pk=1)
        profile.mugshot = "fake_image.png"
        profile.save()

        profile = Profile.objects.get(pk=1)
        self.assertEqual(
            profile.get_mugshot_url(), settings.MEDIA_URL + "fake_image.png"
        )

    def test_stringification(self):
        """ Profile should return a human-readable name as an object """
        profile = Profile.objects.get(pk=1)
        self.assertEqual(profile.__str__(), "Profile of %s" % profile.user.username)

    def test_get_mugshot_url_without_gravatar(self):
        """
        Test if the correct mugshot is returned for the user when
        ``USERENA_MUGSHOT_GRAVATAR`` is set to ``False``.

        """
        # This user has no mugshot, and gravatar is disabled. And to make
        # matters worse, there isn't even a default image.
        userena_settings.USERENA_MUGSHOT_GRAVATAR = False
        profile = Profile.objects.get(pk=1)
        self.assertEqual(profile.get_mugshot_url(), None)

        # There _is_ a default image
        userena_settings.USERENA_MUGSHOT_DEFAULT = "http://example.com"
        profile = Profile.objects.get(pk=1)
        self.assertEqual(profile.get_mugshot_url(), "http://example.com")

        # Settings back to default
        userena_settings.USERENA_MUGSHOT_GRAVATAR = True

    def test_get_mugshot_url_with_gravatar(self):
        """
        Test if the correct mugshot is returned when the user makes use of gravatar.

        """
        profile = Profile.objects.get(pk=1)

        gravatar_hash = hashlib.md5(profile.user.email.encode("utf-8")).hexdigest()

        # Test with the default settings
        mugshot_url = profile.get_mugshot_url()
        parsed = urlparse(mugshot_url)

        self.assertEqual(parsed.netloc, "www.gravatar.com")
        self.assertEqual(parsed.path, "/avatar/" + gravatar_hash)
        self.assertEqual(
            parse_qs(parsed.query),
            parse_qs(
                "s=%(size)s&d=%(default)s"
                % {
                    "size": userena_settings.USERENA_MUGSHOT_SIZE,
                    "default": userena_settings.USERENA_MUGSHOT_DEFAULT,
                }
            ),
        )

        # Change userena settings
        userena_settings.USERENA_MUGSHOT_SIZE = 180
        userena_settings.USERENA_MUGSHOT_DEFAULT = "404"

        # and test again
        mugshot_url = profile.get_mugshot_url()
        parsed = urlparse(mugshot_url)

        self.assertEqual(parsed.netloc, "www.gravatar.com")
        self.assertEqual(parsed.path, "/avatar/" + gravatar_hash)
        self.assertEqual(
            parse_qs(parsed.query),
            parse_qs(
                "s=%(size)s&d=%(default)s"
                % {
                    "size": userena_settings.USERENA_MUGSHOT_SIZE,
                    "default": userena_settings.USERENA_MUGSHOT_DEFAULT,
                }
            ),
        )

        # Settings back to default
        userena_settings.USERENA_MUGSHOT_SIZE = 80
        userena_settings.USERENA_MUGSHOT_DEFAULT = "identicon"

    def test_get_full_name_or_username(self):
        """ Test if the full name or username are returned correcly """
        user = User.objects.get(pk=1)
        profile = get_user_profile(user=user)

        # Profile #1 has a first and last name
        full_name = profile.get_full_name_or_username()
        self.assertEqual(full_name, "John Doe")

        # Let's empty out his name, now we should get his username
        user.first_name = ""
        user.last_name = ""
        user.save()

        self.assertEqual(profile.get_full_name_or_username(), "john")

        # Finally, userena doesn't use any usernames, so we should return the
        # e-mail address.
        userena_settings.USERENA_WITHOUT_USERNAMES = True
        self.assertEqual(profile.get_full_name_or_username(), "john@example.com")
        userena_settings.USERENA_WITHOUT_USERNAMES = False

    def test_can_view_profile(self):
        """ Test if the user can see the profile with three type of users. """
        anon_user = AnonymousUser()
        super_user = User.objects.get(pk=1)
        reg_user = User.objects.get(pk=2)

        profile = Profile.objects.get(pk=1)

        # All users should be able to see a ``open`` profile.
        profile.privacy = "open"
        self.assertTrue(profile.can_view_profile(anon_user))
        self.assertTrue(profile.can_view_profile(super_user))
        self.assertTrue(profile.can_view_profile(reg_user))

        # Registered and super users should be able to see a ``registered``
        # profile.
        profile.privacy = "registered"
        self.assertFalse(profile.can_view_profile(anon_user))
        self.assertTrue(profile.can_view_profile(super_user))
        self.assertTrue(profile.can_view_profile(reg_user))

        # Only superusers can see a closed profile.
        profile.privacy = "closed"
        self.assertFalse(profile.can_view_profile(anon_user))
        self.assertTrue(profile.can_view_profile(super_user))
        self.assertFalse(profile.can_view_profile(reg_user))
