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

import os

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.core.management import call_command
from django.test import TestCase
from django.urls import reverse
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 AdminRegistration, CoachRegistration, StudentRegistration


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")
        self.coach = User.objects.create(email="coach@example.com")
        CoachRegistration.objects.create(user=self.coach, 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(AdminRegistration).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)

    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, 75001 Paris, 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, 75001 Paris, France",
            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",
            birth_date="1980-01-01",
            gender="other",
            address="1 Rue de Rivoli, 75001 Paris, France",
            phone_number="0123456789",
            professional_activity="God",
            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_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(role="Bot")),
                           (self.student, dict(student_class=11, school="Sky", birth_date="2001-01-01",
                                               gender="female", address="1 Rue de Rivoli, 75001 Paris, France",
                                               responsible_name="Toto", responsible_email="toto@example.com")),
                           (self.coach, dict(professional_activity="God", birth_date="2001-01-01",
                                             gender="male", address="1 Rue de Rivoli, 75001 Paris, 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/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/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.
        """
        call_command("rebuild_index", "--noinput", "-v", 0)

        response = self.client.get(reverse("haystack_search") + "?q=" + self.user.email)
        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))
        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.context["object_list"])

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