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

from datetime import timedelta

from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.crypto import get_random_string
from member.models import Membership, Club
from note.models import NoteUser
from oauth2_provider.models import Application, AccessToken

from ..models import Role, Permission


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

    def setUp(self):
        self.user = User.objects.create(
            username="toto",
            password="toto1234",
        )
        self.application = Application.objects.create(
            name="Test",
            client_type=Application.CLIENT_PUBLIC,
            authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
            user=self.user,
        )

    def test_oauth2_access(self):
        """
        Create a simple OAuth2 access token that only has the right to see data of the current user
        and check that this token has required access, and nothing more.
        """

        bde = Club.objects.get(name="BDE")
        view_user_perm = Permission.objects.get(pk=1)  # View own user detail

        # Create access token that has access to our own user detail
        token = AccessToken.objects.create(
            user=self.user,
            application=self.application,
            scope=f"{view_user_perm.pk}_{bde.pk}",
            token=get_random_string(64),
            expires=timezone.now() + timedelta(days=365),
        )

        # No access without token
        resp = self.client.get(f'/api/user/{self.user.pk}/')
        self.assertEqual(resp.status_code, 403)

        # Valid token but user has no membership, so the query is not returning the user object
        resp = self.client.get(f'/api/user/{self.user.pk}/', **{'Authorization': f'Bearer {token.token}'})
        self.assertEqual(resp.status_code, 404)

        # Create membership to validate permissions
        NoteUser.objects.create(user=self.user)
        membership = Membership.objects.create(user=self.user, club_id=bde.pk)
        membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
        membership.save()

        # User is now a member and can now see its own user detail
        resp = self.client.get(f'/api/user/{self.user.pk}/', **{'Authorization': f'Bearer {token.token}'})
        self.assertEqual(resp.status_code, 200)

        # Token is not granted to see profile detail
        resp = self.client.get(f'/api/members/profile/{self.user.profile.pk}/',
                               **{'Authorization': f'Bearer {token.token}'})
        self.assertEqual(resp.status_code, 404)

    def test_scopes(self):
        """
        Ensure that the scopes page is loading.
        """
        self.client.force_login(self.user)

        resp = self.client.get(reverse('permission:scopes'))
        self.assertEqual(resp.status_code, 200)
        self.assertIn(self.application, resp.context['scopes'])
        self.assertNotIn('1_1', resp.context['scopes'][self.application])  # The user has not this permission

        # Create membership to validate permissions
        bde = Club.objects.get(name="BDE")
        NoteUser.objects.create(user=self.user)
        membership = Membership.objects.create(user=self.user, club_id=bde.pk)
        membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
        membership.save()

        resp = self.client.get(reverse('permission:scopes'))
        self.assertEqual(resp.status_code, 200)
        self.assertIn(self.application, resp.context['scopes'])
        self.assertIn('1_1', resp.context['scopes'][self.application])  # Now the user has this permission

    def test_oidc(self):
        """
        Ensure OIDC work
        """
        # Create access token that has access to our own user detail
        token = AccessToken.objects.create(
            user=self.user,
            application=self.application,
            scope="openid",
            token=get_random_string(64),
            expires=timezone.now() + timedelta(days=365),
        )

        # No access without token
        resp = self.client.get('/o/userinfo/')  # userinfo endpoint
        self.assertEqual(resp.status_code, 401)

        # Valid token
        resp = self.client.get('/o/userinfo/', **{'Authorization': f'Bearer {token.token}'})
        self.assertEqual(resp.status_code, 200)

        # Create membership to test api
        NoteUser.objects.create(user=self.user)
        membership = Membership.objects.create(user=self.user, club_id=1)
        membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
        membership.save()

        # Token can always be use to see yourself
        resp = self.client.get('/api/me/',
                               **{'Authorization': f'Bearer {token.token}'})

        # Token is not granted to see other api
        resp = self.client.get(f'/api/members/profile/{self.user.profile.pk}/',
                               **{'Authorization': f'Bearer {token.token}'})

        self.assertEqual(resp.status_code, 404)
