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

import os

from api.tests import TestAPI
from django.contrib.auth.models import User
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from rest_framework.test import APITestCase
from django.urls import reverse
from django.utils import timezone

from ..api.views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet
from ..models import Family, FamilyMembership, Challenge, Achievement


class TestFamily(TestCase):
    """
    Test family
    """

    def setUp(self):
        self.user = User.objects.create_superuser(
            username='admintoto',
            password='toto1234',
            email='toto@example.com',
        )
        self.client.force_login(self.user)

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

        self.family = Family.objects.create(
            name='Test family',
            description='',
        )

        self.challenge = Challenge.objects.create(
            name='Test challenge',
            description='',
            points=100,
        )

        self.achievement = Achievement.objects.create(
            family=self.family,
            challenge=self.challenge,
            valid=False,
        )

    def test_family_list(self):
        """
        Test display family list
        """
        response = self.client.get(reverse("family:family_list"))
        self.assertEqual(response.status_code, 200)

    def test_family_create(self):
        """
        Test create a family
        """
        response = self.client.get(reverse("family:family_create"))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("family:family_create"), data={
            "name": "Family toto",
            "description": "A test family",
        })
        self.assertTrue(Family.objects.filter(name="Family toto").exists())
        self.assertRedirects(response, reverse("family:manage"), 302, 200)

    def test_family_detail(self):
        """
        Test display the detail of a family
        """
        response = self.client.get(reverse("family:family_detail", args=(self.family.pk,)))
        self.assertEqual(response.status_code, 200)

    def test_family_update(self):
        """
        Test update a family
        """
        response = self.client.get(reverse("family:family_update", args=(self.family.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("family:family_update", args=(self.family.pk,)), data=dict(
            name="Toto family updated",
            description="A larger description for the test family"
        ))
        self.assertRedirects(response, self.family.get_absolute_url(), 302, 200)
        self.assertTrue(Family.objects.filter(name="Toto family updated").exists())

    def test_family_update_picture(self):
        """
        Test update the picture of a family
        """
        response = self.client.get(reverse("family:update_pic", args=(self.family.pk,)))
        self.assertEqual(response.status_code, 200)

        old_pic = self.family.display_image

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

        self.family.refresh_from_db()
        self.assertTrue(os.path.exists(self.family.display_image.path))
        os.remove(self.family.display_image.path)

        self.family.display_image = old_pic
        self.family.save()

    def test_family_add_member(self):
        """
        Test add memberships to a family
        """
        response = self.client.get(reverse("family:family_add_member", args=(self.family.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()

        response = self.client.post(reverse("family:family_add_member", args=(self.family.pk,)), data=dict(
            user=user.pk,
        ))
        self.assertRedirects(response, self.family.get_absolute_url(), 302, 200)

        self.assertTrue(FamilyMembership.objects.filter(user=user, family=self.family, year=timezone.now().year).exists())

    def test_challenge_list(self):
        """
        Test display challenge list
        """
        response = self.client.get(reverse('family:challenge_list'))
        self.assertEqual(response.status_code, 200)

    def test_challenge_create(self):
        """
        Test create a challenge
        """
        response = self.client.get(reverse("family:challenge_create"))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("family:challenge_create"), data={
            "name": "Challenge for toto",
            "description": "A test challenge",
            "points": 50,
        })
        self.assertTrue(Challenge.objects.filter(name="Challenge for toto").exists())
        self.assertRedirects(response, reverse("family:manage"), 302, 200)

    def test_challenge_detail(self):
        """
        Test display the detail of a challenge
        """
        response = self.client.get(reverse("family:challenge_detail", args=(self.challenge.pk,)))
        self.assertEqual(response.status_code, 200)

    def test_challenge_update(self):
        """
        Test update a challenge
        """
        response = self.client.get(reverse("family:challenge_update", args=(self.challenge.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("family:challenge_update", args=(self.challenge.pk,)), data=dict(
            name="Challenge updated",
            description="Another description",
            points=10,
        ))
        self.assertRedirects(response, self.challenge.get_absolute_url(), 302, 200)
        self.assertTrue(Challenge.objects.filter(name="Challenge updated").exists())

    def test_render_manage_page(self):
        """
        Test render manage page
        """
        response = self.client.get(reverse("family:manage"))
        self.assertEqual(response.status_code, 200)

    def test_validate_achievement(self):
        """
        Test validate an achievement
        """
        old_family_score = self.family.score

        response = self.client.get(reverse("family:achievement_validate", args=(self.achievement.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.post(reverse("family:achievement_validate", args=(self.achievement.pk,)))
        self.assertRedirects(response, reverse("family:achievement_list"), 302, 200)

        self.achievement.refresh_from_db()
        self.assertIs(self.achievement.valid, True)

        self.family.refresh_from_db()
        self.assertEqual(self.family.score, old_family_score + self.achievement.challenge.points)

    def test_delete_achievement(self):
        """
        Test delete an achievement
        """
        response = self.client.get(reverse("family:achievement_delete", args=(self.achievement.pk,)))
        self.assertEqual(response.status_code, 200)

        response = self.client.delete(reverse("family:achievement_delete", args=(self.achievement.pk,)))
        self.assertRedirects(response, reverse("family:achievement_list"), 302, 200)
        self.assertFalse(Achievement.objects.filter(pk=self.achievement.pk).exists())


class TestBatchAchievements(APITestCase):
    def setUp(self):
        self.user = User.objects.create_superuser(
            username='admintoto',
            password='toto1234',
            email='toto@example.com',
        )
        self.client.force_login(self.user)

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

        self.families = [
            Family.objects.create(name=f'Famille {i}', description='') for i in range(2)
        ]
        self.challenges = [
            Challenge.objects.create(name=f'Challenge {i}', description='', points=50) for i in range(3)
        ]

        self.achievement = Achievement.objects.create(
            family=self.families[0],
            challenge=self.challenges[0],
            valid=False,
        )

        self.url = reverse("family:api:batch_achievements")

    def test_batch_achievement_creation(self):
        family_ids = [f.id for f in self.families]
        challenge_ids = [c.id for c in self.challenges]
        response = self.client.post(
            self.url,
            data={
                'families': family_ids,
                'challenges': challenge_ids
            },
            format='json'
        )

        self.assertEqual(response.status_code, 201)
        for result in response.data['results']:
            if result['family'] == self.families[0].name and result['challenge'] == self.challenges[0].name:
                self.assertEqual(result['status'], 'existed')
            else:
                self.assertEqual(result['status'], 'created')

        expected_count = len(family_ids) * len(challenge_ids)
        self.assertEqual(Achievement.objects.count(), expected_count)

        # Check that correct couples family/challenge exist
        for f in self.families:
            for c in self.challenges:
                self.assertTrue(
                    Achievement.objects.filter(family=f, challenge=c).exists()
                )


class TestFamilyAPI(TestAPI):
    def setUp(self):
        super().setUp()

        self.family = Family.objects.create(
            name='Test family',
            description='',
        )

        self.familymembership = FamilyMembership.objects.create(
            user=self.user,
            family=self.family,
        )

        self.challenge = Challenge.objects.create(
            name='Test challenge',
            description='',
            points=100,
        )

        self.achievement = Achievement.objects.create(
            family=self.family,
            challenge=self.challenge,
            valid=False,
        )

    def test_family_api(self):
        """
        Load Family API page and test all filters and permissions
        """
        self.check_viewset(FamilyViewSet, '/api/family/family/')

    def test_familymembership_api(self):
        """
        Load FamilyMembership API page and test all filters and permissions
        """
        self.check_viewset(FamilyMembershipViewSet, '/api/family/familymembership/')

    def test_challenge_api(self):
        """
        Load Challenge API page and test all filters and permissions
        """
        self.check_viewset(ChallengeViewSet, '/api/family/challenge/')

    def test_achievement_api(self):
        """
        Load Achievement API page and test all filters and permissions
        """
        self.check_viewset(AchievementViewSet, '/api/family/achievement/')
