# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

from datetime import timedelta
import os

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings, TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from participation.models import Team
from tfjm.tokens import email_validation_token

from .models import CoachRegistration, StudentRegistration, VolunteerRegistration


class TestIndexPage(TestCase):
    def test_index(self) -> None:
        """
        Display the index page, without any right.
        """
        response = self.client.get(reverse("index"))
        self.assertEqual(response.status_code, 200)

    def test_not_authenticated(self):
        """
        Try to load some pages without being authenticated.
        """
        response = self.client.get(reverse("registration:reset_admin"))
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:reset_admin"), 302, 200)

        User.objects.create()
        response = self.client.get(reverse("registration:user_detail", args=(1,)))
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:user_detail", args=(1,)))

        Team.objects.create()
        response = self.client.get(reverse("participation:team_detail", args=(1,)))
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:team_detail", args=(1,)))
        response = self.client.get(reverse("participation:update_team", args=(1,)))
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:update_team", args=(1,)))
        response = self.client.get(reverse("participation:create_team"))
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:create_team"))
        response = self.client.get(reverse("participation:join_team"))
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:join_team"))
        response = self.client.get(reverse("participation:team_authorizations", args=(1,)))
        self.assertRedirects(response, reverse("login") + "?next="
                             + reverse("participation:team_authorizations", args=(1,)))
        response = self.client.get(reverse("participation:participation_detail", args=(1,)))
        self.assertRedirects(response, reverse("login") + "?next="
                             + reverse("participation:participation_detail", args=(1,)))


class TestRegistration(TestCase):
    def setUp(self) -> None:
        self.user = User.objects.create_superuser(
            username="admin",
            password="admin",
            email="admin@example.com",
        )
        self.client.force_login(self.user)

        self.student = User.objects.create(email="student@example.com")
        StudentRegistration.objects.create(
            user=self.student,
            student_class=11,
            school="Earth",
            address="1 Rue de Rivoli",
            zip_code=75001,
            city="Paris",
        )
        self.coach = User.objects.create(email="coach@example.com")
        CoachRegistration.objects.create(
            user=self.coach,
            address="1 Rue de Rivoli",
            zip_code=75001,
            city="Paris",
            professional_activity="Teacher",
        )

    def test_admin_pages(self):
        """
        Check that admin pages are rendering successfully.
        """
        response = self.client.get(reverse("admin:index") + "registration/registration/")
        self.assertEqual(response.status_code, 200)

        response = self.client.get(reverse("admin:index")
                                   + f"registration/registration/{self.user.registration.pk}/change/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") +
                                   f"r/{ContentType.objects.get_for_model(VolunteerRegistration).id}/"
                                   f"{self.user.registration.pk}/")
        self.assertRedirects(response, "http://" + Site.objects.get().domain +
                             str(self.user.registration.get_absolute_url()), 302, 200)

        response = self.client.get(reverse("admin:index")
                                   + f"registration/registration/{self.student.registration.pk}/change/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") +
                                   f"r/{ContentType.objects.get_for_model(StudentRegistration).id}/"
                                   f"{self.student.registration.pk}/")
        self.assertRedirects(response, "http://" + Site.objects.get().domain +
                             str(self.student.registration.get_absolute_url()), 302, 200)

        response = self.client.get(reverse("admin:index")
                                   + f"registration/registration/{self.coach.registration.pk}/change/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") +
                                   f"r/{ContentType.objects.get_for_model(CoachRegistration).id}/"
                                   f"{self.coach.registration.pk}/")
        self.assertRedirects(response, "http://" + Site.objects.get().domain +
                             str(self.coach.registration.get_absolute_url()), 302, 200)

    # Ensure that we are between registration dates
    @override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=1),
                                           'close': timezone.now() + timedelta(days=1)})
    def test_registration(self):
        """
        Ensure that the signup form is working successfully.
        """
        response = self.client.get(reverse("registration:signup"))
        self.assertEqual(response.status_code, 200)

        # Incomplete form
        response = self.client.post(reverse("registration:signup"), data=dict(
            last_name="Toto",
            first_name="Toto",
            email="toto@example.com",
            password1="azertyuiopazertyuiop",
            password2="azertyuiopazertyuiop",
            role="participant",
        ))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("registration:signup"), data=dict(
            last_name="Toto",
            first_name="Toto",
            email="toto@example.com",
            password1="azertyuiopazertyuiop",
            password2="azertyuiopazertyuiop",
            role="participant",
            student_class=12,
            school="God",
            birth_date="2000-01-01",
            gender="other",
            address="1 Rue de Rivoli",
            zip_code=75001,
            city="Paris",
            country="France",
            phone_number="0123456789",
            responsible_name="Toto",
            responsible_phone="0123456789",
            responsible_email="toto@example.com",
            give_contact_to_animath=False,
        ))
        self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
        self.assertTrue(User.objects.filter(
            email="toto@example.com",
            registration__participantregistration__studentregistration__responsible_name="Toto").exists())

        # Email is already used
        response = self.client.post(reverse("registration:signup"), data=dict(
            last_name="Toto",
            first_name="Toto",
            email="toto@example.com",
            password1="azertyuiopazertyuiop",
            password2="azertyuiopazertyuiop",
            role="participant",
            student_class=12,
            school="God",
            birth_date="2000-01-01",
            gender="other",
            address="1 Rue de Rivoli",
            zip_code=75001,
            city="Paris",
            phone_number="0123456789",
            responsible_name="Toto",
            responsible_phone="0123456789",
            responsible_email="toto@example.com",
            give_contact_to_animath=False,
        ))
        self.assertEqual(response.status_code, 200)

        response = self.client.get(reverse("registration:email_validation_sent"))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("registration:signup"), data=dict(
            last_name="Toto",
            first_name="Coach",
            email="coachtoto@example.com",
            password1="azertyuiopazertyuiop",
            password2="azertyuiopazertyuiop",
            role="coach",
            gender="other",
            address="1 Rue de Rivoli",
            zip_code=75001,
            city="Paris",
            country="France",
            phone_number="0123456789",
            professional_activity="God",
            last_degree="Master",
            give_contact_to_animath=True,
        ))
        self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
        self.assertTrue(User.objects.filter(email="coachtoto@example.com").exists())

        user = User.objects.get(email="coachtoto@example.com")
        token = email_validation_token.make_token(user)
        uid = urlsafe_base64_encode(force_bytes(user.pk))
        response = self.client.get(reverse("registration:email_validation", kwargs=dict(uidb64=uid, token=token)))
        self.assertEqual(response.status_code, 200)
        user.registration.refresh_from_db()
        self.assertTrue(user.registration.email_confirmed)

        # Token has expired
        response = self.client.get(reverse("registration:email_validation", kwargs=dict(uidb64=uid, token=token)))
        self.assertEqual(response.status_code, 400)

        # Uid does not exist
        response = self.client.get(reverse("registration:email_validation", kwargs=dict(uidb64=0, token="toto")))
        self.assertEqual(response.status_code, 400)

        response = self.client.get(reverse("registration:email_validation_resend", args=(user.pk,)))
        self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)

    def test_registration_dates(self):
        """
        Test that registrations are working only between registration dates.
        """
        self.client.logout()

        # Test that registration between open and close dates are working
        with override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=2),
                                                   'close': timezone.now() + timedelta(days=2)}):
            response = self.client.get(reverse("registration:signup"))
            self.assertEqual(response.status_code, 200)
            self.assertIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
            self.assertNotIn("registrations are not opened", response.content.decode())
            self.assertNotIn("Registrations are closed", response.content.decode())

            response = self.client.post(reverse("registration:signup"))
            self.assertFormError(response.context['form'], None, [])

        # Test that registration before open date is not working
        with override_settings(REGISTRATION_DATES={'open': timezone.now() + timedelta(days=1),
                                                   'close': timezone.now() + timedelta(days=2)}):
            response = self.client.get(reverse("registration:signup"))
            self.assertEqual(response.status_code, 200)
            self.assertNotIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
            self.assertIn("registrations are not opened", response.content.decode())

            response = self.client.post(reverse("registration:signup"))
            self.assertEqual(response.status_code, 200)
            self.assertFormError(response.context['form'], None,
                                 "Registrations are not opened yet. They will open on the "
                                 f"{settings.REGISTRATION_DATES['open']:%Y-%m-%d %H:%M}.")

        # Test that registration after close date is not working
        with override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=2),
                                                   'close': timezone.now() - timedelta(days=1)}):
            response = self.client.get(reverse("registration:signup"))
            self.assertEqual(response.status_code, 200)
            self.assertNotIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
            self.assertIn("Registrations are closed", response.content.decode())

            response = self.client.post(reverse("registration:signup"))
            self.assertEqual(response.status_code, 200)
            self.assertFormError(response.context['form'], None,
                                 "Registrations for this year are closed since "
                                 f"{settings.REGISTRATION_DATES['close']:%Y-%m-%d %H:%M}.")

    def test_login(self):
        """
        With a registered user, try to log in
        """
        response = self.client.get(reverse("login"))
        self.assertEqual(response.status_code, 200)

        self.client.logout()

        response = self.client.post(reverse("login"), data=dict(
            username="admin",
            password="toto",
        ))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("login"), data=dict(
            username="admin@example.com",
            password="admin",
        ))
        self.assertRedirects(response, reverse("index"), 302, 200)

    def test_user_detail(self):
        """
        Load a user detail page.
        """
        response = self.client.get(reverse("registration:my_account_detail"))
        self.assertRedirects(response, reverse("registration:user_detail", args=(self.user.pk,)))

        response = self.client.get(reverse("registration:user_detail", args=(self.user.pk,)))
        self.assertEqual(response.status_code, 200)

    def test_user_list(self):
        """
        Display the list of all users.
        """
        response = self.client.get(reverse("registration:user_list"))
        self.assertEqual(response.status_code, 200)

    def test_update_user(self):
        """
        Update the user information, for each type of user.
        """
        # To test the modification of mailing lists
        from participation.models import Team
        self.student.registration.team = Team.objects.create(
            name="toto",
            trigram="TOT",
        )
        self.student.registration.save()

        for user, data in [(self.user, dict(professional_activity="Bot", admin=True)),
                           (self.student, dict(student_class=11, school="Sky", birth_date="2001-01-01",
                                               gender="female", address="1 Rue de Rivoli", zip_code=75001,
                                               city="Paris", country="France",
                                               responsible_name="Toto",
                                               responsible_phone="0123456789",
                                               responsible_email="toto@example.com")),
                           (self.coach, dict(professional_activity="God", last_degree="Médaille Fields", gender="male",
                                             address="1 Rue de Rivoli", zip_code=75001,
                                             city="Paris", country="France"))]:
            response = self.client.get(reverse("registration:update_user", args=(user.pk,)))
            self.assertEqual(response.status_code, 200)

            response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=dict(
                first_name="Changed",
                last_name="Name",
                email="new_" + user.email,
                give_contact_to_animath=True,
                email_confirmed=True,
                team_id="",
            ))
            self.assertEqual(response.status_code, 200)

            data.update(
                first_name="Changed",
                last_name="Name",
                email="new_" + user.email,
                give_contact_to_animath=True,
                email_confirmed=True,
                team_id="",
            )
            response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=data)
            self.assertRedirects(response, reverse("registration:user_detail", args=(user.pk,)), 302, 200)
            user.refresh_from_db()
            self.assertEqual(user.email, user.username)
            self.assertFalse(user.registration.email_confirmed)
            self.assertEqual(user.first_name, "Changed")

    def test_upload_photo_authorization(self):
        """
        Try to upload a photo authorization.
        """
        for auth_type in ["photo_authorization", "health_sheet", "parental_authorization"]:
            response = self.client.get(reverse("registration:upload_user_photo_authorization",
                                               args=(self.student.registration.pk,)))
            self.assertEqual(response.status_code, 200)

            # README is not a valid PDF file
            response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
                                                args=(self.student.registration.pk,)), data={
                auth_type: open("README.md", "rb"),
            })
            self.assertEqual(response.status_code, 200)

            # Don't send too large files
            response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
                                                args=(self.student.registration.pk,)), data={
                auth_type: SimpleUploadedFile("file.pdf", content=int(0).to_bytes(2000001, "big"),
                                              content_type="application/pdf"),
            })
            self.assertEqual(response.status_code, 200)

            response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
                                                args=(self.student.registration.pk,)), data={
                auth_type: open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
            })
            self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)

            self.student.registration.refresh_from_db()
            self.assertTrue(getattr(self.student.registration, auth_type))

            response = self.client.get(reverse(
                auth_type, args=(getattr(self.student.registration, auth_type).name.split('/')[-1],)))
            self.assertEqual(response.status_code, 200)

        from participation.models import Team
        team = Team.objects.create(name="Test", trigram="TES")
        self.student.registration.team = team
        self.student.registration.save()
        response = self.client.get(reverse("participation:team_authorizations", args=(team.pk,)))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response["content-type"], "application/zip")

        # Do it twice, ensure that the previous authorization got deleted
        old_authoratization = self.student.registration.photo_authorization.path
        response = self.client.post(reverse("registration:upload_user_photo_authorization",
                                            args=(self.student.registration.pk,)), data=dict(
            photo_authorization=open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
        ))
        self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
        self.assertFalse(os.path.isfile(old_authoratization))

        self.student.registration.refresh_from_db()
        self.student.registration.photo_authorization.delete()
        self.student.registration.save()

    def test_user_detail_forbidden(self):
        """
        Create a new user and ensure that it can't see the detail of another user.
        """
        self.client.force_login(self.coach)

        response = self.client.get(reverse("registration:user_detail", args=(self.user.pk,)))
        self.assertEqual(response.status_code, 403)

        response = self.client.get(reverse("registration:update_user", args=(self.user.pk,)))
        self.assertEqual(response.status_code, 403)

        response = self.client.get(reverse("registration:upload_user_photo_authorization",
                                           args=(self.student.registration.pk,)))
        self.assertEqual(response.status_code, 403)

        response = self.client.get(reverse("photo_authorization", args=("inexisting-authorization",)))
        self.assertEqual(response.status_code, 404)

        with open("media/authorization/photo/example", "w") as f:
            f.write("I lost the game.")
        self.student.registration.photo_authorization = "authorization/photo/example"
        self.student.registration.save()
        response = self.client.get(reverse("photo_authorization", args=("example",)))
        self.assertEqual(response.status_code, 403)
        os.remove("media/authorization/photo/example")

    def test_impersonate(self):
        """
        Admin can impersonate other people to act as them.
        """
        response = self.client.get(reverse("registration:user_impersonate", args=(0x7ffff42ff,)))
        self.assertEqual(response.status_code, 404)

        # Impersonate student account
        response = self.client.get(reverse("registration:user_impersonate", args=(self.student.pk,)))
        self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
        self.assertEqual(self.client.session["_fake_user_id"], self.student.id)

        # Reset admin view
        response = self.client.get(reverse("registration:reset_admin"))
        self.assertRedirects(response, reverse("index"), 302, 200)
        self.assertFalse("_fake_user_id" in self.client.session)

    def test_research(self):
        """
        Try to search some things.
        """

        response = self.client.get(reverse("haystack_search") + "?q=" + self.user.email + "&models=auth.user")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.context["object_list"])

        response = self.client.get(reverse("haystack_search") + "?q=" +
                                   str(self.coach.registration.professional_activity)
                                   + "&models=registration.CoachRegistration")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.context["object_list"])

        response = self.client.get(reverse("haystack_search") + "?q=" +
                                   self.student.registration.school + "&models=registration.StudentRegistration")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.context["object_list"])
