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

from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core.management import call_command
from django.test import TestCase
from django.urls import reverse
from registration.models import CoachRegistration, StudentRegistration

from .models import Participation, Team, Tournament


class TestStudentParticipation(TestCase):
    def setUp(self) -> None:
        self.superuser = User.objects.create_superuser(
            username="admin",
            email="admin@example.com",
            password="toto1234",
        )

        self.user = User.objects.create(
            first_name="Toto",
            last_name="Toto",
            email="toto@example.com",
            password="toto",
        )
        StudentRegistration.objects.create(
            user=self.user,
            student_class=12,
            school="Earth",
            give_contact_to_animath=True,
            email_confirmed=True,
        )
        self.team = Team.objects.create(
            name="Super team",
            trigram="AAA",
            access_code="azerty",
        )
        self.client.force_login(self.user)

        self.second_user = User.objects.create(
            first_name="Lalala",
            last_name="Lalala",
            email="lalala@example.com",
            password="lalala",
        )
        StudentRegistration.objects.create(
            user=self.second_user,
            student_class=11,
            school="Moon",
            give_contact_to_animath=True,
            email_confirmed=True,
        )
        self.second_team = Team.objects.create(
            name="Poor team",
            trigram="FFF",
            access_code="qwerty",
        )

        self.coach = User.objects.create(
            first_name="Coach",
            last_name="Coach",
            email="coach@example.com",
            password="coach",
        )
        CoachRegistration.objects.create(user=self.coach)

        self.tournament = Tournament.objects.create(
            name="France",
            place="Here",
        )

    def test_admin_pages(self):
        """
        Load Django-admin pages.
        """
        self.client.force_login(self.superuser)

        # Test team pages
        response = self.client.get(reverse("admin:index") + "participation/team/")
        self.assertEqual(response.status_code, 200)

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

        # Test participation pages
        self.team.participation.valid = True
        self.team.participation.save()
        response = self.client.get(reverse("admin:index") + "participation/participation/")
        self.assertEqual(response.status_code, 200)

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

    def test_create_team(self):
        """
        Try to create a team.
        """
        response = self.client.get(reverse("participation:create_team"))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("participation:create_team"), data=dict(
            name="Test team",
            trigram="123",
        ))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("participation:create_team"), data=dict(
            name="Test team",
            trigram="TES",
        ))
        self.assertTrue(Team.objects.filter(trigram="TES").exists())
        team = Team.objects.get(trigram="TES")
        self.assertRedirects(response, reverse("participation:team_detail", args=(team.pk,)), 302, 200)

        # Already in a team
        response = self.client.post(reverse("participation:create_team"), data=dict(
            name="Test team 2",
            trigram="TET",
        ))
        self.assertEqual(response.status_code, 403)

    def test_join_team(self):
        """
        Try to join an existing team.
        """
        response = self.client.get(reverse("participation:join_team"))
        self.assertEqual(response.status_code, 200)

        team = Team.objects.create(name="Test", trigram="TES")

        response = self.client.post(reverse("participation:join_team"), data=dict(
            access_code="éééééé",
        ))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("participation:join_team"), data=dict(
            access_code=team.access_code,
        ))
        self.assertRedirects(response, reverse("participation:team_detail", args=(team.pk,)), 302, 200)
        self.assertTrue(Team.objects.filter(trigram="TES").exists())

        # Already joined
        response = self.client.post(reverse("participation:join_team"), data=dict(
            access_code=team.access_code,
        ))
        self.assertEqual(response.status_code, 403)

    def test_team_list(self):
        """
        Test to display the list of teams.
        """
        response = self.client.get(reverse("participation:team_list"))
        self.assertTrue(response.status_code, 200)

    def test_no_myteam_redirect_noteam(self):
        """
        Test redirection.
        """
        response = self.client.get(reverse("participation:my_team_detail"))
        self.assertTrue(response.status_code, 200)

    def test_team_detail(self):
        """
        Try to display the information of a team.
        """
        self.user.registration.team = self.team
        self.user.registration.save()

        response = self.client.get(reverse("participation:my_team_detail"))
        self.assertRedirects(response, reverse("participation:team_detail", args=(self.team.pk,)), 302, 200)

        response = self.client.get(reverse("participation:team_detail", args=(self.team.pk,)))
        self.assertEqual(response.status_code, 200)

        # Can't see other teams
        self.second_user.registration.team = self.second_team
        self.second_user.registration.save()
        self.client.force_login(self.second_user)
        response = self.client.get(reverse("participation:team_detail", args=(self.team.participation.pk,)))
        self.assertEqual(response.status_code, 403)

    def test_request_validate_team(self):
        """
        The team ask for validation.
        """
        self.user.registration.team = self.team
        self.user.registration.save()

        second_user = User.objects.create(
            first_name="Blublu",
            last_name="Blublu",
            email="blublu@example.com",
            password="blublu",
        )
        StudentRegistration.objects.create(
            user=second_user,
            student_class=12,
            school="Jupiter",
            give_contact_to_animath=True,
            email_confirmed=True,
            team=self.team,
            photo_authorization="authorization/photo/mai-linh",
            health_sheet="authorization/health/mai-linh",
            parental_authorization="authorization/parental/mai-linh",
        )

        third_user = User.objects.create(
            first_name="Zupzup",
            last_name="Zupzup",
            email="zupzup@example.com",
            password="zupzup",
        )
        StudentRegistration.objects.create(
            user=third_user,
            student_class=10,
            school="Sun",
            give_contact_to_animath=False,
            email_confirmed=True,
            team=self.team,
            photo_authorization="authorization/photo/yohann",
            health_sheet="authorization/health/yohann",
            parental_authorization="authorization/parental/yohann",
        )

        fourth_user = User.objects.create(
            first_name="tfjm",
            last_name="tfjm",
            email="tfjm@example.com",
            password="tfjm",
        )
        StudentRegistration.objects.create(
            user=fourth_user,
            student_class=10,
            school="Sun",
            give_contact_to_animath=False,
            email_confirmed=True,
            team=self.team,
            photo_authorization="authorization/photo/tfjm",
            health_sheet="authorization/health/tfjm",
            parental_authorization="authorization/parental/tfjm",
        )

        self.coach.registration.team = self.team
        self.coach.registration.health_sheet = "authorization/health/coach"
        self.coach.registration.photo_authorization = "authorization/photo/coach"
        self.coach.registration.email_confirmed = True
        self.coach.registration.save()

        self.client.force_login(self.superuser)
        # Admin users can't ask for validation
        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="RequestValidationForm",
            engagement=True,
        ))
        self.assertEqual(resp.status_code, 200)

        self.client.force_login(self.user)

        self.assertIsNone(self.team.participation.valid)

        resp = self.client.get(reverse("participation:team_detail", args=(self.team.pk,)))
        self.assertEqual(resp.status_code, 200)
        self.assertFalse(resp.context["can_validate"])
        # Can't validate
        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="RequestValidationForm",
            engagement=True,
        ))
        self.assertEqual(resp.status_code, 200)

        self.user.registration.photo_authorization = "authorization/photo/ananas"
        self.user.registration.health_sheet = "authorization/health/ananas"
        self.user.registration.parental_authorization = "authorization/parental/ananas"
        self.user.registration.save()

        resp = self.client.get(reverse("participation:team_detail", args=(self.team.pk,)))
        self.assertEqual(resp.status_code, 200)
        self.assertFalse(resp.context["can_validate"])

        self.team.participation.tournament = self.tournament
        self.team.participation.save()
        self.team.motivation_letter = "i_am_motivated.pdf"
        self.team.save()
        resp = self.client.get(reverse("participation:team_detail", args=(self.team.pk,)))
        self.assertEqual(resp.status_code, 200)
        self.assertTrue(resp.context["can_validate"])

        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="RequestValidationForm",
            engagement=True,
        ))
        self.assertRedirects(resp, reverse("participation:team_detail", args=(self.team.pk,)), 302, 200)
        self.team.participation.refresh_from_db()
        self.assertFalse(self.team.participation.valid)
        self.assertIsNotNone(self.team.participation.valid)

        # Team already asked for validation
        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="RequestValidationForm",
            engagement=True,
        ))
        self.assertEqual(resp.status_code, 200)

    def test_validate_team(self):
        """
        A team asked for validation. Try to validate it.
        """
        self.team.participation.valid = False
        self.team.participation.tournament = self.tournament
        self.team.participation.save()

        self.tournament.organizers.add(self.superuser.registration)
        self.tournament.save()

        # No right to do that
        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="ValidateParticipationForm",
            message="J'ai 4 ans",
            validate=True,
        ))
        self.assertEqual(resp.status_code, 200)

        self.client.force_login(self.superuser)

        resp = self.client.get(reverse("participation:team_detail", args=(self.team.pk,)))
        self.assertEqual(resp.status_code, 200)

        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="ValidateParticipationForm",
            message="Woops I didn't said anything",
        ))
        self.assertEqual(resp.status_code, 200)

        # Test invalidate team
        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="ValidateParticipationForm",
            message="Wsh nope",
            invalidate=True,
        ))
        self.assertRedirects(resp, reverse("participation:team_detail", args=(self.team.pk,)), 302, 200)
        self.team.participation.refresh_from_db()
        self.assertIsNone(self.team.participation.valid)

        # Team did not ask validation
        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="ValidateParticipationForm",
            message="Bienvenue ça va être trop cool",
            validate=True,
        ))
        self.assertEqual(resp.status_code, 200)

        self.team.participation.tournament = self.tournament
        self.team.participation.valid = False
        self.team.participation.save()

        # Test validate team
        resp = self.client.post(reverse("participation:team_detail", args=(self.team.pk,)), data=dict(
            _form_type="ValidateParticipationForm",
            message="Bienvenue ça va être trop cool",
            validate=True,
        ))
        self.assertRedirects(resp, reverse("participation:team_detail", args=(self.team.pk,)), 302, 200)
        self.team.participation.refresh_from_db()
        self.assertTrue(self.team.participation.valid)

    def test_update_team(self):
        """
        Try to update team information.
        """
        self.user.registration.team = self.team
        self.user.registration.save()

        self.coach.registration.team = self.team
        self.coach.registration.save()

        self.team.participation.tournament = self.tournament
        self.team.participation.save()

        response = self.client.get(reverse("participation:update_team", args=(self.team.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("participation:update_team", args=(self.team.pk,)), data=dict(
            name="Updated team name",
            trigram="BBB",
        ))
        self.assertRedirects(response, reverse("participation:team_detail", args=(self.team.pk,)), 302, 200)
        self.assertTrue(Team.objects.filter(trigram="BBB").exists())

    def test_leave_team(self):
        """
        A user is in a team, and leaves it.
        """
        # User is not in a team
        response = self.client.post(reverse("participation:team_leave"))
        self.assertEqual(response.status_code, 403)

        self.user.registration.team = self.team
        self.user.registration.save()

        # Team is valid
        self.team.participation.valid = True
        self.team.participation.save()
        response = self.client.post(reverse("participation:team_leave"))
        self.assertEqual(response.status_code, 403)

        # Unauthenticated users are redirected to login page
        self.client.logout()
        response = self.client.get(reverse("participation:team_leave"))
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:team_leave"), 302, 200)

        self.client.force_login(self.user)

        self.team.participation.valid = None
        self.team.participation.save()

        response = self.client.post(reverse("participation:team_leave"))
        self.assertRedirects(response, reverse("index"), 302, 200)
        self.user.registration.refresh_from_db()
        self.assertIsNone(self.user.registration.team)
        self.assertFalse(Team.objects.filter(pk=self.team.pk).exists())

    def test_no_myparticipation_redirect_nomyparticipation(self):
        """
        Ensure a permission denied when we search my team participation when we are in no team.
        """
        response = self.client.get(reverse("participation:my_participation_detail"))
        self.assertEqual(response.status_code, 403)

    def test_participation_detail(self):
        """
        Try to display the detail of a team participation.
        """
        self.user.registration.team = self.team
        self.user.registration.save()

        # Can't see the participation if it is not valid
        response = self.client.get(reverse("participation:my_participation_detail"))
        self.assertRedirects(response,
                             reverse("participation:participation_detail", args=(self.team.participation.pk,)),
                             302, 403)

        self.team.participation.valid = True
        self.team.participation.save()
        response = self.client.get(reverse("participation:my_participation_detail"))
        self.assertRedirects(response,
                             reverse("participation:participation_detail", args=(self.team.participation.pk,)),
                             302, 200)

        response = self.client.get(reverse("participation:participation_detail", args=(self.team.participation.pk,)))
        self.assertEqual(response.status_code, 200)

        # Can't see other participations
        self.second_user.registration.team = self.second_team
        self.second_user.registration.save()
        self.client.force_login(self.second_user)
        response = self.client.get(reverse("participation:participation_detail", args=(self.team.participation.pk,)))
        self.assertEqual(response.status_code, 403)

    def test_forbidden_access(self):
        """
        Load personal pages and ensure that these are protected.
        """
        self.user.registration.team = self.team
        self.user.registration.save()

        resp = self.client.get(reverse("participation:team_detail", args=(self.second_team.pk,)))
        self.assertEqual(resp.status_code, 403)
        resp = self.client.get(reverse("participation:update_team", args=(self.second_team.pk,)))
        self.assertEqual(resp.status_code, 403)
        resp = self.client.get(reverse("participation:team_authorizations", args=(self.second_team.pk,)))
        self.assertEqual(resp.status_code, 403)
        resp = self.client.get(reverse("participation:participation_detail", args=(self.second_team.pk,)))
        self.assertEqual(resp.status_code, 403)


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

        self.team1 = Team.objects.create(
            name="Toto",
            trigram="TOT",
        )
        self.team1.participation.valid = True
        self.team1.participation.problem = 1
        self.team1.participation.save()

        self.team2 = Team.objects.create(
            name="Bliblu",
            trigram="BIU",
        )
        self.team2.participation.valid = True
        self.team2.participation.problem = 1
        self.team2.participation.save()

        self.team3 = Team.objects.create(
            name="Zouplop",
            trigram="ZPL",
        )
        self.team3.participation.valid = True
        self.team3.participation.problem = 1
        self.team3.participation.save()

        self.other_team = Team.objects.create(
            name="I am different",
            trigram="IAD",
        )
        self.other_team.participation.valid = True
        self.other_team.participation.problem = 2
        self.other_team.participation.save()

    def test_research(self):
        """
        Try to search some things.
        """
        call_command("rebuild_index", "--noinput", "--verbosity", 0)

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

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

    def test_create_team_forbidden(self):
        """
        Ensure that an admin can't create a team.
        """
        response = self.client.post(reverse("participation:create_team"), data=dict(
            name="Test team",
            trigram="TES",
        ))
        self.assertEqual(response.status_code, 403)

    def test_join_team_forbidden(self):
        """
        Ensure that an admin can't join a team.
        """
        team = Team.objects.create(name="Test", trigram="TES")

        response = self.client.post(reverse("participation:join_team"), data=dict(
            access_code=team.access_code,
        ))
        self.assertTrue(response.status_code, 403)

    def test_leave_team_forbidden(self):
        """
        Ensure that an admin can't leave a team.
        """
        response = self.client.get(reverse("participation:team_leave"))
        self.assertTrue(response.status_code, 403)

    def test_my_team_forbidden(self):
        """
        Ensure that an admin can't access to "My team".
        """
        response = self.client.get(reverse("participation:my_team_detail"))
        self.assertEqual(response.status_code, 403)

    def test_my_participation_forbidden(self):
        """
        Ensure that an admin can't access to "My participation".
        """
        response = self.client.get(reverse("participation:my_participation_detail"))
        self.assertEqual(response.status_code, 403)