from datetime import timedelta
import uuid

from django.contrib.auth import get_user_model
from django.utils import timezone
from django.test import TestCase

from ..api.serializers import EncryptionKeySerializer, EncryptionKeyRevoke
from ..choices import STATUS
from .data import (
    PEM_PUBLIC_RSA,
    PEM_PUBLIC_ED25519,
    PEM_PUBLIC_RSA_INVALID,
    PEM_PUBLIC_ED25519_INVALID,
)
from ..models import EncryptionKey

User = get_user_model()


class TestEncryptionKeySerializer(TestCase):
    def setUp(self) -> None:
        self.user = User.objects.create_user(username="foo", is_superuser=True)
        self.user.refresh_from_db()

    def test_rsa_serializer(self):
        serializer = EncryptionKeySerializer(
            data={
                'name': 'RSA Cert',
                'user': self.user.cid,
                'key': PEM_PUBLIC_RSA.decode(),
                'status': STATUS.active,
                'kid': str(uuid.uuid1()),
                'revocation_date': None,
            },
        )
        self.assertTrue(serializer.is_valid(), serializer.errors)
        cert = serializer.save()
        self.assertIsInstance(cert, EncryptionKey)

    def test_ed25519_serializer(self):
        serializer = EncryptionKeySerializer(
            data={
                'name': 'ED25519 Cert',
                'user': self.user.cid,
                'key': PEM_PUBLIC_ED25519.decode(),
                'status': STATUS.active,
                'kid': str(uuid.uuid1()),
                'revocation_date': None,
            },
        )
        self.assertTrue(serializer.is_valid(), serializer.errors)
        cert = serializer.save()
        self.assertIsInstance(cert, EncryptionKey)

    def test_serializer_auto_generates_kid(self):
        """
        Pyseto KID should be auto-created when kid is None or empty string.
        """
        data = {
            'name': 'Auto KID Cert',
            'user': self.user.cid,
            'key': PEM_PUBLIC_ED25519.decode(),
            'status': STATUS.active,
            'kid': '',
            'revocation_date': None,
        }
        serializer = EncryptionKeySerializer(data=data)
        self.assertTrue(serializer.is_valid(), serializer.errors)
        self.assertTrue(serializer.validated_data['kid'].startswith('k4.pid'))

        data['kid'] = None
        data['key'] = PEM_PUBLIC_RSA.decode()
        serializer = EncryptionKeySerializer(data=data)
        self.assertTrue(serializer.is_valid(), serializer.errors)
        self.assertTrue(serializer.validated_data['kid'].startswith('k1.pid'))

        cert = serializer.save()
        self.assertIsInstance(cert, EncryptionKey)

    def test_invalid_serializer(self):
        for invalid_cert in (
            'not-a-valid-cert',
            '-----BEGIN PUBLIC KEY-----\nstillnotvalid\n-----END PUBLIC KEY-----',
            PEM_PUBLIC_ED25519_INVALID,
            PEM_PUBLIC_RSA_INVALID,
            None,
            (),
            b'nonsense',
        ):
            serializer = EncryptionKeySerializer(
                data={
                    'name': 'ED25519 Cert',
                    'user': self.user.cid,
                    'key': invalid_cert,
                    'status': STATUS.active,
                    'kid': str(uuid.uuid1()),
                    'revocation_date': None,
                },
            )
            self.assertFalse(serializer.is_valid())
            self.assertIn('key', serializer.errors)

    def test_revocation_serializer(self):
        serializer = EncryptionKeySerializer(
            data={
                'name': 'Cert to Revoke',
                'user': self.user.cid,
                'key': PEM_PUBLIC_ED25519.decode(),
                'status': STATUS.active,
                'kid': str(uuid.uuid1()),
                'revocation_date': None,
            },
        )
        self.assertTrue(serializer.is_valid(), serializer.errors)
        cert = serializer.save()
        self.assertIsInstance(cert, EncryptionKey)

        # -----------------------------------------------------
        # Test revocation date in the past
        data = {
            'status': STATUS.replaced,
            'revocation_date': timezone.now() - timedelta(days=2),
        }
        serializer = EncryptionKeyRevoke(data=data, instance=cert)
        self.assertFalse(serializer.is_valid())
        self.assertIn(
            'revocation_date',
            serializer.errors,
            msg='Revocation date in the past is not valid',
        )

        # -----------------------------------------------------
        # Test revocation date in the future
        data['revocation_date'] = timezone.now() + timedelta(days=2)
        serializer = EncryptionKeyRevoke(data=data, instance=cert)
        self.assertFalse(serializer.is_valid())
        self.assertIn(
            'revocation_date',
            serializer.errors,
            msg='Revocation date in the future is not valid',
        )

        # -----------------------------------------------------
        # Test present validation date
        data['revocation_date'] = timezone.now()
        serializer = EncryptionKeyRevoke(data=data, instance=cert)
        self.assertTrue(serializer.is_valid(), msg=serializer.errors)

        serializer.save()
        cert.refresh_from_db()
        self.assertEqual(cert.status, STATUS.replaced)
