from aspen_crypto.keys import Ed25519PrivateKey
import uuid
from lcrequests.tokens import V4PasetoToken, RegistrationPasetoToken
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APIClient

from console_base.utils.test_utils import CatchSignal
from ..models import EncryptionKey
from ..choices import STATUS, SUBJECT
from ..signals import record_saved_via_umbrella_api

User = get_user_model()


class EncryptionKeyViewSetTest(TestCase):
    def setUp(self):
        cache.clear()
        self.user = User.objects.create_user(username="foo", is_superuser=True)
        self.private_key = Ed25519PrivateKey.generate()
        self.key_bytes = self.private_key.public_key.as_pem.strip()
        self.key_text = self.key_bytes.decode()
        self.public_key_obj = EncryptionKey.objects.create(
            name='ed255919-drf',
            user=self.user,
            key=self.key_text,
        )

    def test_encryption_key_list(self):
        api_client = APIClient()
        response = api_client.get(reverse("api-encryptionkey-list"))
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

        token = V4PasetoToken(
            private_key=self.private_key,
            user_cid=self.user.cid,
        ).auth_header()
        api_client.credentials(HTTP_AUTHORIZATION=token)
        response = api_client.get(reverse("api-encryptionkey-list"))
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

        key_data = response.data[0]
        self.assertEqual(key_data['key'], self.key_text)

    def test_create_public_key(self):
        api_client = APIClient()
        token = V4PasetoToken(
            private_key=self.private_key,
            user_cid=self.user.cid,
        ).auth_header()
        api_client.credentials(HTTP_AUTHORIZATION=token)

        new_private_key = Ed25519PrivateKey.generate()
        new_key_text = new_private_key.public_key.as_pem.strip().decode()

        key_data = {
            'name': f'new key for {self.user}',
            'user': str(self.user.cid),
            'key': new_key_text,
            'kid': new_private_key.public_key.as_paseto(version=4).to_paserk_id(),
            'revocation_date': '',
        }

        # Ensure that signal is sent when API call is made, when authorized by Paseto token
        with CatchSignal(record_saved_via_umbrella_api) as handler:
            response = api_client.post(reverse("api-encryptionkey-list"), data=key_data)
            self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.data)
            self.assertEqual(response.data['key'], key_data['key'])
            handler.assert_called_once()
            signal_args = handler.call_args[1]
            self.assertEqual(signal_args['sender'], EncryptionKey)
            self.assertTrue(signal_args['created'])

    def test_revoke_encryption_key(self):
        private_key = Ed25519PrivateKey.generate()
        key_bytes = private_key.public_key.as_pem.strip()
        public_key = EncryptionKey.objects.create(
            name='ed255919-to-revoke',
            user=self.user,
            key=key_bytes.decode(),
        )
        self.assertIsInstance(public_key, EncryptionKey)

        api_client = APIClient()
        token = V4PasetoToken(
            private_key=private_key,
            user_cid=self.user.cid,
        ).auth_header()
        api_client.credentials(HTTP_AUTHORIZATION=token)

        data = {
            'status': STATUS.replaced,
            'revocation_date': timezone.now(),
        }
        url = reverse("api-encryptionkey-detail", kwargs={'cid': public_key.cid})
        response = api_client.put(f'{url}revoke_encryption_key/', data=data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        public_key.refresh_from_db()
        self.assertEqual(public_key.status, STATUS.replaced)
        self.assertEqual(public_key.revocation_date, data['revocation_date'])

    def test_registration_key_not_permitted(self):
        """
        Revoking keys is not permitted by tokens signed "setup" role.
        """
        private_key = Ed25519PrivateKey.generate()
        key_bytes = private_key.public_key.as_pem.strip()
        public_key = EncryptionKey.objects.create(
            name='ed255919-to-revoke',
            user=self.user,
            key=key_bytes.decode(),
            subject=SUBJECT.Clavis,
        )
        self.assertIsInstance(public_key, EncryptionKey)

        api_client = APIClient()
        RegistrationPasetoToken.subject = SUBJECT.Clavis

        token = RegistrationPasetoToken(
            user_cid=self.user.cid,
            private_key=private_key,
            subject_cid=uuid.uuid1(),
        ).auth_header()
        api_client.credentials(HTTP_AUTHORIZATION=token)

        data = {
            'status': STATUS.replaced,
            'revocation_date': timezone.now(),
        }
        url = reverse("api-encryptionkey-detail", kwargs={'cid': public_key.cid})
        response = api_client.put(f'{url}revoke_encryption_key/', data=data)
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
