# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

import hashlib
import os
from datetime import date, timedelta

from api.tests import TestAPI
from django.contrib.auth.models import User
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.models import Q
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from note.models import Alias, NoteSpecial
from permission.models import Role
from treasury.models import SogeCredit

from ..api.views import ClubViewSet, MembershipViewSet, ProfileViewSet
from ..models import Club, Membership, Profile

"""
Create some users and clubs and test that all pages are rendering properly
and that memberships are working.
"""


class TestMemberships(TestCase):
    fixtures = ('initial', )

    def setUp(self) -> None:
        """
        Create a sample superuser, a club and a membership for all tests.
        """
        self.user = User.objects.create_superuser(
            username="toto",
            email="toto@example.com",
            password="toto",
        )
        self.user.profile.registration_valid = True
        self.user.profile.email_confirmed = True
        self.user.profile.save()
        self.client.force_login(self.user)

        sess = self.client.session
        sess["permission_mask"] = 42
        sess.save()

        self.club = Club.objects.create(name="totoclub", parent_club=Club.objects.get(name="BDE"))
        self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
        self.membership = Membership.objects.create(user=self.user, club=self.club)
        self.membership.roles.add(Role.objects.get(name="Bureau de club"))
        self.membership.save()

    def test_admin_pages(self):
        """
        Check that Django Admin pages for the member app are loading successfully.
        """
        response = self.client.get(reverse("admin:index") + "member/membership/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "member/club/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "auth/user/")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("admin:index") + "auth/user/" + str(self.user.pk) + "/change/")
        self.assertEqual(response.status_code, 200)

    def test_render_club_list(self):
        """
        Render the list of all clubs, with a search.
        """
        response = self.client.get(reverse("member:club_list"))
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("member:club_list") + "?search=toto")
        self.assertEqual(response.status_code, 200)

    def test_render_club_create(self):
        """
        Try to create a new club.
        """
        response = self.client.get(reverse("member:club_create"))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("member:club_create"), data=dict(
            name="Club toto",
            email="clubtoto@example.com",
            parent_club=self.club.pk,
            require_memberships=False,
            membership_fee_paid=0,
            membership_fee_unpaid=0,
        ))
        self.assertTrue(Club.objects.filter(name="Club toto").exists())
        club = Club.objects.get(name="Club toto")
        self.assertRedirects(response, club.get_absolute_url(), 302, 200)

    def test_render_club_detail(self):
        """
        Display the detail of a club.
        """
        response = self.client.get(reverse("member:club_detail", args=(self.club.pk,)))
        self.assertEqual(response.status_code, 200)

    def test_render_club_update(self):
        """
        Try to update the information about a club.
        """
        response = self.client.get(reverse("member:club_update", args=(self.club.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("member:club_update", args=(self.club.pk, )), data=dict(
            name="Toto club updated",
            email="clubtoto@example.com",
            require_memberships=True,
            membership_fee_paid=0,
            membership_fee_unpaid=0,
        ))
        self.assertRedirects(response, self.club.get_absolute_url(), 302, 200)
        self.assertTrue(Club.objects.exclude(name="Toto club updated"))

    def test_render_club_update_picture(self):
        """
        Try to update the picture of the note of a club.
        """
        response = self.client.get(reverse("member:club_update_pic", args=(self.club.pk,)))
        self.assertEqual(response.status_code, 200)

        old_pic = self.club.note.display_image

        with open("apps/member/static/member/img/default_picture.png", "rb") as f:
            image = SimpleUploadedFile("image.png", f.read(), "image/png")
            response = self.client.post(reverse("member:club_update_pic", args=(self.club.pk,)), dict(
                image=image,
                x=0,
                y=0,
                width=200,
                height=200,
            ))
            self.assertRedirects(response, self.club.get_absolute_url(), 302, 200)

        self.club.note.refresh_from_db()
        self.assertTrue(os.path.exists(self.club.note.display_image.path))
        os.remove(self.club.note.display_image.path)

        self.club.note.display_image = old_pic
        self.club.note.save()

    def test_render_club_aliases(self):
        """
        Display the list of the aliases of a club.
        """
        # Alias creation and deletion is already tested in the note app
        response = self.client.get(reverse("member:club_alias", args=(self.club.pk,)))
        self.assertEqual(response.status_code, 200)

    def test_render_club_members(self):
        """
        Display the list of the members of a club.
        """
        response = self.client.get(reverse("member:club_members", args=(self.club.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.get(reverse("member:club_members", args=(self.club.pk,)) + "?search=toto&roles="
                                   + ",".join([str(role.pk) for role in
                                               Role.objects.filter(weirole__isnull=True).all()]))
        self.assertEqual(response.status_code, 200)

    def test_render_club_add_member(self):
        """
        Try to add memberships and renew them.
        """
        response = self.client.get(reverse("member:club_add_member", args=(Club.objects.get(name="BDE").pk,)))
        self.assertEqual(response.status_code, 200)

        user = User.objects.create(username="totototo")
        user.profile.registration_valid = True
        user.profile.email_confirmed = True
        user.profile.save()
        user.save()

        # We create a club without any parent and one club with parent BDE (that is the club Kfet)
        for bde_parent in False, True:
            if bde_parent:
                club = Club.objects.get(name="Kfet")
            else:
                club = Club.objects.create(
                    name="Second club without BDE",
                    parent_club=None,
                    email="newclub@example.com",
                    require_memberships=True,
                    membership_fee_paid=1000,
                    membership_fee_unpaid=500,
                    membership_start=date.today(),
                    membership_end=date.today() + timedelta(days=366),
                    membership_duration=366,
                )

            response = self.client.get(reverse("member:club_add_member", args=(club.pk,)))
            self.assertEqual(response.status_code, 200)

            # Create a new membership
            response = self.client.post(reverse("member:club_add_member", args=(club.pk,)), data=dict(
                user=user.pk,
                date_start="{:%Y-%m-%d}".format(date.today()),
                soge=False,
                credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
                credit_amount=4200,
                last_name="TOTO",
                first_name="Toto",
                bank="Le matelas",
            ))
            self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)

            self.assertTrue(Membership.objects.filter(user=user, club=club).exists())

            # Membership is sent to the past to check renewals
            membership = Membership.objects.get(user=user, club=club)
            self.assertTrue(membership.valid)
            membership.date_start = date(year=2000, month=1, day=1)
            membership.date_end = date(year=2000, month=12, day=31)
            membership.save()
            self.assertFalse(membership.valid)

            response = self.client.get(reverse("member:club_members", args=(club.pk,)) + "?only_active=0")
            self.assertEqual(response.status_code, 200)

            bde_membership = self.bde_membership
            if bde_parent:
                bde_membership = Membership.objects.get(club__name="BDE", user=user)
                bde_membership.date_start = date(year=2000, month=1, day=1)
                bde_membership.date_end = date(year=2000, month=12, day=31)
                bde_membership.save()

            response = self.client.get(reverse("member:club_renew_membership", args=(bde_membership.pk,)))
            self.assertEqual(response.status_code, 200)

            response = self.client.get(reverse("member:club_renew_membership", args=(membership.pk,)))
            self.assertEqual(response.status_code, 200)

            # Renew membership
            response = self.client.post(reverse("member:club_renew_membership", args=(membership.pk,)), data=dict(
                user=user.pk,
                date_start="{:%Y-%m-%d}".format(date.today()),
                soge=bde_parent,
                credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
                credit_amount=14242,
                last_name="TOTO",
                first_name="Toto",
                bank="Bank",
            ))
            self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)

            response = self.client.get(club.get_absolute_url())
            self.assertEqual(response.status_code, 200)

    def test_auto_join_kfet_when_join_bde_with_soge(self):
        """
        When we join the BDE club with a Soge registration, a Kfet membership is automatically created.
        We check that it is the case.
        """
        user = User.objects.create(username="new1A")
        user.profile.registration_valid = True
        user.profile.email_confirmed = True
        user.profile.save()
        user.save()

        bde = Club.objects.get(name="BDE")
        kfet = Club.objects.get(name="Kfet")

        response = self.client.post(reverse("member:club_add_member", args=(bde.pk,)), data=dict(
            user=user.pk,
            date_start="{:%Y-%m-%d}".format(date.today()),
            soge=True,
            credit_type=NoteSpecial.objects.get(special_type="Virement bancaire").id,
            credit_amount=(bde.membership_fee_paid + kfet.membership_fee_paid) / 100,
            last_name="TOTO",
            first_name="Toto",
            bank="Société générale",
        ))
        self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)

        self.assertTrue(Membership.objects.filter(user=user, club=bde).exists())
        self.assertTrue(Membership.objects.filter(user=user, club=kfet).exists())
        self.assertTrue(SogeCredit.objects.filter(user=user).exists())

    def test_change_roles(self):
        """
        Check to change the roles of a membership.
        """
        response = self.client.get(reverse("member:club_manage_roles", args=(self.membership.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict(
            roles=[role.id for role in Role.objects.filter(
                Q(name="Membre de club") | Q(name="Trésorièr⋅e de club") | Q(name="Bureau de club")).all()],
        ))
        self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
        self.membership.refresh_from_db()
        self.assertEqual(self.membership.roles.count(), 3)

    def test_render_user_list(self):
        """
        Display the user search page.
        """
        response = self.client.get(reverse("member:user_list"))
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("member:user_list") + "?search=toto")
        self.assertEqual(response.status_code, 200)

    def test_render_user_detail(self):
        """
        Display the user detail page.
        """
        response = self.client.get(self.user.profile.get_absolute_url())
        self.assertEqual(response.status_code, 200)

    def test_render_user_update(self):
        """
        Update some data about the user.
        """
        response = self.client.get(reverse("member:user_update_profile", args=(self.user.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("member:user_update_profile", args=(self.user.pk,)), data=dict(
            first_name="Toto",
            last_name="Toto",
            username="toto changed",
            email="updated@example.com",
            phone_number="+33600000000",
            section="",
            department="A0",
            promotion=timezone.now().year,
            address="Earth",
            paid=True,
            ml_events_registration="en",
            ml_sports_registration=True,
            ml_art_registration=True,
            report_frequency=7,
            VSS_charter_read=True
        ))
        self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
        self.assertTrue(User.objects.filter(username="toto changed").exists())
        self.assertTrue(Profile.objects.filter(address="Earth").exists())
        self.assertTrue(Alias.objects.filter(normalized_name="totochanged").exists())

    def test_render_user_update_picture(self):
        """
        Update the note picture of the user.
        """
        response = self.client.get(reverse("member:user_update_pic", args=(self.user.pk,)))
        self.assertEqual(response.status_code, 200)

        old_pic = self.user.note.display_image

        with open("apps/member/static/member/img/default_picture.png", "rb") as f:
            image = SimpleUploadedFile("image.png", f.read(), "image/png")
            response = self.client.post(reverse("member:user_update_pic", args=(self.user.pk,)), dict(
                image=image,
                x=0,
                y=0,
                width=200,
                height=200,
            ))
            self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)

        self.user.note.refresh_from_db()
        self.assertTrue(os.path.exists(self.user.note.display_image.path))
        os.remove(self.user.note.display_image.path)

        self.user.note.display_image = old_pic
        self.user.note.save()

    def test_render_user_aliases(self):
        """
        Display the list of aliases of the user.
        """
        # Alias creation and deletion is already tested in the note app
        response = self.client.get(reverse("member:user_alias", args=(self.user.pk,)))
        self.assertEqual(response.status_code, 200)

    def test_manage_auth_token(self):
        """
        Display the page to see the API authentication token, see it and regenerate it.
        :return:
        """
        response = self.client.get(reverse("member:auth_token"))
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("member:auth_token") + "?view")
        self.assertEqual(response.status_code, 200)
        response = self.client.get(reverse("member:auth_token") + "?regenerate")
        self.assertRedirects(response, reverse("member:auth_token") + "?view", 302, 200)

    def test_random_coverage(self):
        # Useless, only for coverage
        self.assertEqual(str(self.user), str(self.user.profile))
        self.user.profile.promotion = None
        self.assertEqual(self.user.profile.ens_year, 0)
        self.membership.date_end = None
        self.assertTrue(self.membership.valid)

    def test_nk15_hasher(self):
        """
        Test that NK15 passwords are successfully imported.
        """
        salt = "42"
        password = "strongpassword42"
        hashed = hashlib.sha256((salt + password).encode("utf-8")).hexdigest()
        self.user.password = "custom_nk15$1$" + salt + "|" + hashed
        self.user.save()
        self.assertTrue(self.user.check_password(password))


class TestMemberAPI(TestAPI):
    def setUp(self) -> None:
        super().setUp()

        self.user.profile.registration_valid = True
        self.user.profile.email_confirmed = True
        self.user.profile.phone_number = "0600000000"
        self.user.profile.section = "1A0"
        self.user.profile.department = "A0"
        self.user.profile.address = "Earth"
        self.user.profile.save()

        self.club = Club.objects.create(
            name="totoclub",
            parent_club=Club.objects.get(name="BDE"),
            membership_start=date(year=1970, month=1, day=1),
            membership_end=date(year=2040, month=1, day=1),
            membership_duration=365 * 10,
        )
        self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
        self.membership = Membership.objects.create(user=self.user, club=self.club)
        self.membership.roles.add(Role.objects.get(name="Bureau de club"))
        self.membership.save()

    def test_club_api(self):
        """
        Load Club API page and test all filters and permissions
        """
        self.check_viewset(ClubViewSet, "/api/members/club/")

    def test_profile_api(self):
        """
        Load Profile API page and test all filters and permissions
        """
        self.check_viewset(ProfileViewSet, "/api/members/profile/")

    def test_membership_api(self):
        """
        Load Membership API page and test all filters and permissions
        """
        self.check_viewset(MembershipViewSet, "/api/members/membership/")
