"""Test API Authentication"""

import json
from base64 import b64encode
from unittest.mock import patch

from django.conf import settings
from django.test import TestCase
from django.utils import timezone
from rest_framework.exceptions import AuthenticationFailed

from authentik.api.authentication import IPCUser, TokenAuthentication
from authentik.blueprints.tests import reconcile_app
from authentik.common.oauth.constants import SCOPE_AUTHENTIK_API
from authentik.core.models import Token, TokenIntents, UserTypes
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.lib.generators import generate_id
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider


class TestAPIAuth(TestCase):
    """Test API Authentication"""

    def test_invalid_type(self):
        """Test invalid type"""
        self.assertIsNone(TokenAuthentication().bearer_auth(b"foo bar"))

    def test_invalid_empty(self):
        """Test invalid type"""
        self.assertIsNone(TokenAuthentication().bearer_auth(b"Bearer "))
        self.assertIsNone(TokenAuthentication().bearer_auth(b""))

    def test_invalid_no_token(self):
        """Test invalid with no token"""
        auth = b64encode(b":abc").decode()
        self.assertIsNone(TokenAuthentication().bearer_auth(f"Basic :{auth}".encode()))

    def test_bearer_valid(self):
        """Test valid token"""
        token = Token.objects.create(intent=TokenIntents.INTENT_API, user=create_test_admin_user())
        user, tk = TokenAuthentication().bearer_auth(f"Bearer {token.key}".encode())
        self.assertEqual(user, token.user)
        self.assertEqual(token, token)

    def test_bearer_valid_deactivated(self):
        """Test valid token"""
        user = create_test_admin_user()
        user.is_active = False
        user.save()
        token = Token.objects.create(intent=TokenIntents.INTENT_API, user=user)
        with self.assertRaises(AuthenticationFailed):
            TokenAuthentication().bearer_auth(f"Bearer {token.key}".encode())

    @reconcile_app("authentik_outposts")
    def test_managed_outpost_fail(self):
        """Test managed outpost"""
        outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first()
        outpost.user.delete()
        outpost.delete()
        with self.assertRaises(AuthenticationFailed):
            TokenAuthentication().bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())

    @reconcile_app("authentik_outposts")
    def test_managed_outpost_success(self):
        """Test managed outpost"""
        user, outpost = TokenAuthentication().bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
        self.assertEqual(user.type, UserTypes.INTERNAL_SERVICE_ACCOUNT)
        self.assertEqual(outpost, Outpost.objects.filter(managed=MANAGED_OUTPOST).first())

    def test_jwt_valid(self):
        """Test valid JWT"""
        provider = OAuth2Provider.objects.create(
            name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
        )
        access = AccessToken.objects.create(
            user=create_test_admin_user(),
            provider=provider,
            token=generate_id(),
            auth_time=timezone.now(),
            _scope=SCOPE_AUTHENTIK_API,
            _id_token=json.dumps({}),
        )
        user, token = TokenAuthentication().bearer_auth(f"Bearer {access.token}".encode())
        self.assertEqual(user, access.user)
        self.assertEqual(token, access)

    def test_jwt_missing_scope(self):
        """Test valid JWT"""
        provider = OAuth2Provider.objects.create(
            name=generate_id(), client_id=generate_id(), authorization_flow=create_test_flow()
        )
        access = AccessToken.objects.create(
            user=create_test_admin_user(),
            provider=provider,
            token=generate_id(),
            auth_time=timezone.now(),
            _scope="",
            _id_token=json.dumps({}),
        )
        with self.assertRaises(AuthenticationFailed):
            TokenAuthentication().bearer_auth(f"Bearer {access.token}".encode())

    def test_ipc(self):
        """Test IPC auth (mock key)"""
        key = generate_id()
        with patch("authentik.api.authentication.ipc_key", key):
            user, ctx = TokenAuthentication().bearer_auth(f"Bearer {key}".encode())
        self.assertEqual(user, IPCUser())
        self.assertEqual(ctx, None)
