from aspen_crypto.keys import generate_kid
from datetime import timedelta
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework import exceptions
from rest_framework.serializers import CharField, DateTimeField

from console_base.api.serializers import (
    LCModelSerializer,
    LCHyperlinkedIdentityField,
    CanonicalIDField,
)

from encipher.models import EncryptionKey

BASE_READ_ONLY_FIELDS = (
    'pk',
    'url',
    'created',
    'modified',
)

User = get_user_model()


# ---------------------------------------------------------------------------
class EncryptionKeySerializer(LCModelSerializer):
    url = LCHyperlinkedIdentityField(view_name='api-encryptionkey-detail')
    user = CanonicalIDField(queryset=User.objects.all())
    kid = CharField(allow_null=True, allow_blank=True)
    revocation_date = DateTimeField(allow_null=True)

    # Allow blank values until other migrations are complete
    status = CharField(allow_blank=True, required=False)

    class Meta:
        model = EncryptionKey
        fields = (
            'pk',
            'url',
            'created',
            'modified',
            'cid',
            'is_active',
            'user',
            'name',
            'key',
            'kid',
            'status',
            'subject',
            'last_used_on',
            'revocation_date',
        )
        read_only_fields = BASE_READ_ONLY_FIELDS

    def validate(self, attrs):
        """
        Ensure the KID value is present so the UniqueConstraint
        validation on the `kid` field succeeds.
        """
        attrs = super().validate(attrs)
        if (key := attrs.get('key')) and not attrs.get('kid'):
            attrs['kid'] = generate_kid(key)
        return attrs


# ---------------------------------------------------------------------------
class EncryptionKeySerializerLite(LCModelSerializer):
    """
    Serializer with the minimal fields required for creation of Encryption Keys.
    Used for initial registering of new Encryption Keys from devices.
    """

    url = LCHyperlinkedIdentityField(view_name='api-encryptionkey-detail')
    user = CanonicalIDField(queryset=User.objects.all(), required=False, allow_null=True)

    class Meta:
        model = EncryptionKey
        fields = (
            'pk',
            'url',
            'created',
            'modified',
            'cid',
            'name',
            'key',
            'user',
        )
        read_only_fields = BASE_READ_ONLY_FIELDS


# ---------------------------------------------------------------------------
class EncryptionKeyDataTables(LCModelSerializer):
    class Meta:
        model = EncryptionKey
        fields = (
            'pk',
            'url',
            'created',
            'modified',
            'cid',
            'is_active',
            'user',
            'name',
            'kid',
            'status',
            'subject',
            'last_used_on',
            'revocation_date',
        )
        read_only_fields = BASE_READ_ONLY_FIELDS


# ---------------------------------------------------------------------------
class EncryptionKeyRevoke(LCModelSerializer):
    """
    Serializer to enable revoking of Encryption Keys.
    """

    revocation_date = DateTimeField()

    class Meta:
        model = EncryptionKey
        fields = (
            'status',
            'revocation_date',
        )
        read_only_fields = ('url',)

    def validate_revocation_date(self, value):
        if not value:
            return value

        if value < timezone.now() - timedelta(days=1):
            raise exceptions.ValidationError(
                'Revocation date must not be more than 1 day in the past'
            )
        if value > timezone.now() + timedelta(days=1):
            raise exceptions.ValidationError(
                'Revocation date must not be more than 1 day in the future'
            )

        return value


__all__ = (
    'EncryptionKeySerializer',
    'EncryptionKeyDataTables',
    'EncryptionKeySerializerLite',
    'EncryptionKeyRevoke',
)
