"""Unit Tests for the socialprofile module user"""

# pylint: disable=R0904, C0103

from unittest import skipIf, skipUnless
from unittest.mock import patch

import django
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.middleware import AuthenticationMiddleware
from django.core import mail, management
from django.db import connection
from django.http import HttpRequest
from django.test import TestCase
from django.utils import timezone

try:
    from django.utils.encoding import force_text
except ImportError:
    # Only available from Django 4, ignore the tests otherwise
    from django.utils.encoding import force_str

# from socialprofile.forms import EmailUserChangeForm, EmailUserCreationForm

try:
    from django.contrib.auth.middleware import SessionAuthenticationMiddleware
except ImportError:
    # Only available from Django 1.7, ignore the tests otherwise
    SessionAuthenticationMiddleware = None


class UserTest(TestCase):
    user_email = "newuser@localhost.local"
    user_password = "1234"

    def create_user(self):
        """Create and return a new user with self.user_email as login and self.user_password as password."""
        return get_user_model().objects.create_user(self.user_email, self.user_password)

    def test_user_creation(self):
        # Create a new user saving the time frame
        right_now = timezone.now().replace(
            microsecond=0
        )  # MySQL doesn't store microseconds
        with patch.object(timezone, "now", return_value=right_now):
            self.create_user()

        # Check user exists and email is correct
        self.assertEqual(get_user_model().objects.all().count(), 1)
        self.assertEqual(get_user_model().objects.all()[0].email, self.user_email)

        # Check date_joined and last_login dates
        self.assertEqual(get_user_model().objects.all()[0].date_joined, right_now)
        self.assertEqual(get_user_model().objects.all()[0].last_login, right_now)

        # Check flags
        self.assertTrue(get_user_model().objects.all()[0].is_active)
        self.assertFalse(get_user_model().objects.all()[0].is_staff)
        self.assertFalse(get_user_model().objects.all()[0].is_superuser)

    def test_user_get_full_name(self):
        user = self.create_user()
        self.assertEqual(user.get_full_name(), self.user_email)

    def test_user_get_short_name(self):
        user = self.create_user()
        self.assertEqual(user.get_short_name(), self.user_email)

    def test_email_user(self):
        # Email definition
        subject = "Email Subject"
        message = "Email Message"
        from_email = "from@normal.com"

        user = self.create_user()

        # Test that no message exists
        self.assertEqual(len(mail.outbox), 0)

        # Send test email
        user.email_user(subject, message, from_email)

        # Test that one message has been sent
        self.assertEqual(len(mail.outbox), 1)

        # Verify that the email is correct
        self.assertEqual(mail.outbox[0].subject, subject)
        self.assertEqual(mail.outbox[0].body, message)
        self.assertEqual(mail.outbox[0].from_email, from_email)
        self.assertEqual(mail.outbox[0].to, [user.email])

    def test_email_user_kwargs(self):
        # valid send_mail parameters
        kwargs = {
            "fail_silently": False,
            "auth_user": None,
            "auth_password": None,
            "connection": None,
        }
        user = get_user_model()(email="foo@bar.com")
        user.email_user(
            subject="Subject here",
            message="This is a message",
            from_email="from@domain.com",
            **kwargs,
        )
        # Test that one message has been sent.
        self.assertEqual(len(mail.outbox), 1)
        # Verify that test email contains the correct attributes:
        message = mail.outbox[0]
        self.assertEqual(message.subject, "Subject here")
        self.assertEqual(message.body, "This is a message")
        self.assertEqual(message.from_email, "from@domain.com")
        self.assertEqual(message.to, [user.email])


class UserManagerTest(TestCase):
    def test_create_user(self):
        email_lowercase = "normal@user.test"
        user = get_user_model().objects.create_user(email_lowercase)
        self.assertEqual(user.email, email_lowercase)
        self.assertFalse(user.has_usable_password())
        self.assertTrue(user.is_active)
        self.assertFalse(user.is_staff)
        self.assertFalse(user.is_superuser)

    def test_create_superuser(self):
        email_lowercase = "super@user.test"
        password = "password1234$%&/"
        user = get_user_model().objects.create_superuser(email_lowercase, password)
        self.assertEqual(user.email, email_lowercase)
        self.assertTrue(user.check_password, password)
        self.assertTrue(user.is_active)
        self.assertTrue(user.is_staff)
        self.assertTrue(user.is_superuser)

    def test_user_creation_is_active(self):
        # Create deactivated user
        email_lowercase = "disabled@user.test"
        password = "password1234$%&/"
        user = get_user_model().objects.create_user(
            email_lowercase, password, is_active=False
        )
        self.assertFalse(user.is_active)

    def test_user_creation_is_staff(self):
        # Create staff user
        email_lowercase = "staff@user.test"
        password = "password1234$%&/"
        user = get_user_model().objects.create_user(
            email_lowercase, password, is_staff=True
        )
        self.assertTrue(user.is_staff)

    def test_create_user_email_domain_normalize_rfc3696(self):
        # According to http://tools.ietf.org/html/rfc3696#section-3
        # the "@" symbol can be part of the local part of an email address
        returned = get_user_model().objects.normalize_email(r"Abc\@DEF@EXAMPLE.com")
        self.assertEqual(returned, r"Abc\@DEF@example.com")

    def test_create_user_email_domain_normalize(self):
        returned = get_user_model().objects.normalize_email("normal@DOMAIN.COM")
        self.assertEqual(returned, "normal@domain.com")

    def test_create_user_email_domain_normalize_with_whitespace(self):
        returned = get_user_model().objects.normalize_email(
            r"email\ with_whitespace@D.COM"
        )
        self.assertEqual(returned, r"email\ with_whitespace@d.com")

    def test_empty_username(self):
        self.assertRaisesMessage(
            ValueError,
            "The given email must be set",
            get_user_model().objects.create_user,
            email="",
        )


@skipIf(django.VERSION < (1, 7, 0), "Migrations not available in this Django version")
class MigrationsTest(TestCase):
    def test_makemigrations_no_changes(self):
        try:
            from StringIO import StringIO
        except ImportError:
            from io import StringIO

        with patch("sys.stdout", new_callable=StringIO) as mock:
            management.call_command("makemigrations", "socialprofile", dry_run=True)
        self.assertEqual(
            mock.getvalue(), "No changes detected in app 'socialprofile'\n"
        )

    @skipUnless(django.VERSION[:2] == (1, 7), "Monkey patch only applied to Django 1.7")
    def test_monkey_patch_model_field(self):
        field_last_login = get_user_model()._meta.get_field("last_login")
        self.assertTrue(field_last_login.null)
        self.assertTrue(field_last_login.blank)

    @skipUnless(django.VERSION[:2] == (1, 7), "Monkey patch only applied to Django 1.7")
    def test_monkey_patch_db_column(self):
        table_name = get_user_model()._meta.db_table
        cursor = connection.cursor()
        table_fields = connection.introspection.get_table_description(
            cursor, table_name
        )
        field = next(
            field for field in table_fields if field.name == "last_login"
        )  # pragma: no cover
        self.assertTrue(field.null_ok)

    @skipUnless(django.VERSION[:2] == (1, 7), "Monkey patch only applied to Django 1.7")
    def test_monkey_patch_side_effects(self):
        # Check the parent model isn't affected from the monkey patch
        from django.contrib.auth.models import AbstractBaseUser

        field_last_login = AbstractBaseUser._meta.get_field("last_login")
        self.assertFalse(field_last_login.null)
        self.assertFalse(field_last_login.blank)


@skipIf(
    SessionAuthenticationMiddleware is None,
    "SessionAuthenticationMiddleware not available in this version",
)
class TestSessionAuthenticationMiddleware(TestCase):
    def setUp(self):
        self.user_email = "test@example.com"
        self.user_password = "test_password"
        self.user_username = "test_username"
        self.user = get_user_model().objects.create_user(
            self.user_email, self.user_password, self.user_username
        )

        self.middleware_auth = AuthenticationMiddleware()
        self.middleware_session_auth = SessionAuthenticationMiddleware()
        self.assertTrue(
            self.client.login(
                username=self.user_email,
                password=self.user_password,
            )
        )
        self.request = HttpRequest()
        self.request.session = self.client.session

    def test_changed_password_doesnt_invalidate_session(self):
        # Changing a user's password shouldn't invalidate the session if session
        # verification isn't activated.
        session_key = self.request.session.session_key
        self.middleware_auth.process_request(self.request)
        self.middleware_session_auth.process_request(self.request)
        self.assertIsNotNone(self.request.user)
        self.assertFalse(self.request.user.is_anonymous())

        # After password change, user should remain logged in.
        self.user.set_password("new_password")
        self.user.save()
        self.middleware_auth.process_request(self.request)
        self.middleware_session_auth.process_request(self.request)
        self.assertIsNotNone(self.request.user)
        self.assertFalse(self.request.user.is_anonymous())
        self.assertEqual(session_key, self.request.session.session_key)

    def test_changed_password_invalidates_session_with_middleware(self):
        session_key = self.request.session.session_key
        with self.modify_settings(
            MIDDLEWARE_CLASSES={
                "append": [
                    "django.contrib.auth.middleware.SessionAuthenticationMiddleware"
                ]
            }
        ):
            # After password change, user should be anonymous
            self.user.set_password("new_password")
            self.user.save()
            self.middleware_auth.process_request(self.request)
            self.middleware_session_auth.process_request(self.request)
            self.assertIsNotNone(self.request.user)
            self.assertTrue(self.request.user.is_anonymous())
        # session should be flushed
        self.assertNotEqual(session_key, self.request.session.session_key)


# @override_settings(
#     USE_TZ=False, PASSWORD_HASHERS=("django.contrib.auth.hashers.SHA1PasswordHasher",)
# )
# class EmailUserCreationFormTest(TestCase):
#     def setUp(self):
#         get_user_model().objects.create_user("testclient@example.com", "test123")
#
#     def test_user_already_exists(self):
#         data = {
#             "email": "testclient@example.com",
#             "password1": "test123",
#             "password2": "test123",
#         }
#         form = EmailUserCreationForm(data)
#         self.assertFalse(form.is_valid())
#         self.assertEqual(
#             form["email"].errors, [force_text(form.error_messages["duplicate_email"])]
#         )
#
#     def test_invalid_data(self):
#         data = {
#             "email": "testclient",
#             "password1": "test123",
#             "password2": "test123",
#         }
#         form = EmailUserCreationForm(data)
#         self.assertFalse(form.is_valid())
#         self.assertEqual(form["email"].errors, [_("Enter a valid email address.")])
#
#     def test_password_verification(self):
#         # The verification password is incorrect.
#         data = {
#             "email": "testclient@example.com",
#             "password1": "test123",
#             "password2": "test",
#         }
#         form = EmailUserCreationForm(data)
#         self.assertFalse(form.is_valid())
#         self.assertEqual(
#             form["password2"].errors,
#             [force_text(form.error_messages["password_mismatch"])],
#         )
#
#     def test_both_passwords(self):
#         # One (or both) passwords weren't given
#         data = {"email": "testclient@example.com"}
#         form = EmailUserCreationForm(data)
#         required_error = [force_text(Field.default_error_messages["required"])]
#         self.assertFalse(form.is_valid())
#         self.assertEqual(form["password1"].errors, required_error)
#         self.assertEqual(form["password2"].errors, required_error)
#
#         data["password2"] = "test123"
#         form = EmailUserCreationForm(data)
#         self.assertFalse(form.is_valid())
#         self.assertEqual(form["password1"].errors, required_error)
#         self.assertEqual(form["password2"].errors, [])
#
#     def test_success(self):
#         # The success case.
#         data = {
#             "email": "jsmith@example.com",
#             "password1": "test123",
#             "password2": "test123",
#         }
#         form = EmailUserCreationForm(data)
#         self.assertTrue(form.is_valid())
#         u = form.save()
#         self.assertEqual(
#             repr(u), "<%s: jsmith@example.com>" % get_user_model().__name__
#         )
#         self.assertIsNotNone(u.pk)
#
#     def test_success_without_commit(self):
#         # The success case, but without saving the user instance to the db.
#         data = {
#             "email": "jsmith@example.com",
#             "password1": "test123",
#             "password2": "test123",
#         }
#         form = EmailUserCreationForm(data)
#         self.assertTrue(form.is_valid())
#         u = form.save(commit=False)
#         self.assertEqual(
#             repr(u), "<%s: jsmith@example.com>" % get_user_model().__name__
#         )
#         self.assertIsNone(u.pk, None)


# @override_settings(
#     USE_TZ=False, PASSWORD_HASHERS=("django.contrib.auth.hashers.SHA1PasswordHasher",)
# )
# class EmailUserChangeFormTest(TestCase):
#     def setUp(self):
#         user = get_user_model().objects.create_user("testclient@example.com")
#         user.password = "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161"
#         user.save()
#         get_user_model().objects.create_user("empty_password@example.com")
#         user_unmanageable = get_user_model().objects.create_user(
#             "unmanageable_password@example.com"
#         )
#         user_unmanageable.password = "$"
#         user_unmanageable.save()
#         user_unknown = get_user_model().objects.create_user(
#             "unknown_password@example.com"
#         )
#         user_unknown.password = "foo$bar"
#         user_unknown.save()
#
#     def test_username_validity(self):
#         user = get_user_model().objects.get(email="testclient@example.com")
#         data = {"email": "not valid"}
#         form = EmailUserChangeForm(data, instance=user)
#         self.assertFalse(form.is_valid())
#         self.assertEqual(form["email"].errors, [_("Enter a valid email address.")])
#
#     def test_bug_14242(self):
#         # A regression test, introduce by adding an optimization for the
#         # EmailUserChangeForm.
#
#         class MyUserForm(EmailUserChangeForm):
#             def __init__(self, *args, **kwargs):
#                 super().__init__(*args, **kwargs)
#                 self.fields[
#                     "groups"
#                 ].help_text = "These groups give users different permissions"
#
#             class Meta(EmailUserChangeForm.Meta):
#                 fields = ("groups",)
#
#         # Just check we can create it
#         MyUserForm({})
#
#     def test_unsuable_password(self):
#         user = get_user_model().objects.get(email="empty_password@example.com")
#         user.set_unusable_password()
#         user.save()
#         form = EmailUserChangeForm(instance=user)
#         self.assertIn(_("No password set."), form.as_table())
#
#     def test_bug_17944_empty_password(self):
#         user = get_user_model().objects.get(email="empty_password@example.com")
#         form = EmailUserChangeForm(instance=user)
#         self.assertIn(_("No password set."), form.as_table())
#
#     def test_bug_17944_unmanageable_password(self):
#         user = get_user_model().objects.get(email="unmanageable_password@example.com")
#         form = EmailUserChangeForm(instance=user)
#         self.assertIn(
#             _("Invalid password format or unknown hashing algorithm."), form.as_table()
#         )
#
#     def test_bug_17944_unknown_password_algorithm(self):
#         user = get_user_model().objects.get(email="unknown_password@example.com")
#         form = EmailUserChangeForm(instance=user)
#         self.assertIn(
#             _("Invalid password format or unknown hashing algorithm."), form.as_table()
#         )
#
#     def test_bug_19133(self):
#         """The change form does not return the password value."""
#         # Use the form to construct the POST data
#         user = get_user_model().objects.get(email="testclient@example.com")
#         form_for_data = EmailUserChangeForm(instance=user)
#         post_data = form_for_data.initial
#
#         # The password field should be readonly, so anything
#         # posted here should be ignored; the form will be
#         # valid, and give back the 'initial' value for the
#         # password field.
#         post_data["password"] = "new password"
#         form = EmailUserChangeForm(instance=user, data=post_data)
#
#         self.assertTrue(form.is_valid())
#         self.assertEqual(
#             form.cleaned_data["password"],
#             "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
#         )
#
#     def test_bug_19349_bound_password_field(self):
#         user = get_user_model().objects.get(email="testclient@example.com")
#         form = EmailUserChangeForm(data={}, instance=user)
#         # When rendering the bound password field,
#         # ReadOnlyPasswordHashWidget needs the initial
#         # value to render correctly
#         self.assertEqual(form.initial["password"], form["password"].value())


class EmailUserAdminTest(TestCase):
    def setUp(self):
        self.user_email = "test@example.com"
        self.user_password = "test_password"
        self.user_username = "test_username"
        self.user = get_user_model().objects.create_user(
            self.user_email, self.user_password, self.user_username
        )
        self.user.set_password(self.user_password)

        if settings.AUTH_USER_MODEL == "custom_user.EmailUser":
            self.app_name = "custom_user"
            self.model_name = "emailuser"
            self.model_verbose_name = "user"
            self.model_verbose_name_plural = "Users"
            if django.VERSION[:2] < (1, 7):
                self.app_verbose_name = "Custom_User"
            else:
                self.app_verbose_name = "Custom User"
        if settings.AUTH_USER_MODEL == "test_custom_user_subclass.MyCustomEmailUser":
            self.app_name = "test_custom_user_subclass"
            self.model_name = "mycustomemailuser"
            self.model_verbose_name = "MyCustomEmailUserVerboseName"
            self.model_verbose_name_plural = "MyCustomEmailUserVerboseNamePlural"
            if django.VERSION[:2] < (1, 7):
                self.app_verbose_name = "Test_Custom_User_Subclass"
            else:
                self.app_verbose_name = "Test Custom User Subclass"

    # def test_url(self):
    #     self.assertTrue(
    #         self.client.login(
    #             username=self.user_email,
    #             password=self.user_password,
    #         )
    #     )
    #     response = self.client.get(reverse("admin:app_list", args=(self.app_name,)))
    #     self.assertEqual(response.status_code, 200)
    #
    # def test_app_name(self):
    #     self.assertTrue(
    #         self.client.login(
    #             username=self.user_email,
    #             password=self.user_password,
    #         )
    #     )
    #
    #     response = self.client.get(reverse("admin:app_list", args=(self.app_name,)))
    #     self.assertEqual(response.context["app_list"][0]["name"], self.app_verbose_name)
    #
    # def test_model_name(self):
    #     self.assertTrue(
    #         self.client.login(
    #             username=self.user_email,
    #             password=self.user_password,
    #         )
    #     )
    #
    #     response = self.client.get(
    #         reverse(f"admin:{self.app_name}_{self.model_name}_changelist")
    #     )
    #     self.assertEqual(
    #         force_text(response.context["title"]),
    #         "Select %s to change" % self.model_verbose_name,
    #     )
    #
    # def test_model_name_plural(self):
    #     self.assertTrue(
    #         self.client.login(
    #             username=self.user_email,
    #             password=self.user_password,
    #         )
    #     )
    #
    #     response = self.client.get(reverse("admin:app_list", args=(self.app_name,)))
    #     self.assertEqual(
    #         force_text(response.context["app_list"][0]["models"][0]["name"]),
    #         self.model_verbose_name_plural,
    #     )
    #
    # def test_user_change_password(self):
    #     self.assertTrue(
    #         self.client.login(
    #             username=self.user_email,
    #             password=self.user_password,
    #         )
    #     )
    #
    #     user_change_url = reverse(
    #         f"admin:{self.app_name}_{self.model_name}_change",
    #         args=(self.user.pk,),
    #     )
    #
    #     if django.VERSION[:2] < (1, 8):
    #         # Since reverse() does not exist yet, create path manually
    #         password_change_url = user_change_url + "password/"
    #     else:
    #         password_change_url = reverse(
    #             "admin:auth_user_password_change", args=(self.user.pk,)
    #         )
    #
    #     response = self.client.get(user_change_url)
    #     # Test the link inside password field help_text.
    #     rel_link = re.search(
    #         r'you can change the password using <a href="([^"]*)">this form</a>',
    #         force_text(response.content),
    #     ).groups()[0]
    #     self.assertEqual(
    #         os.path.normpath(user_change_url + rel_link),
    #         os.path.normpath(password_change_url),
    #     )
    #
    #     # Test url is correct.
    #     self.assertEqual(
    #         self.client.get(password_change_url).status_code,
    #         200,
    #     )
