"""WebAuthn stage"""

from django.contrib.auth import get_user_model
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer, Serializer
from webauthn.helpers.base64url_to_bytes import base64url_to_bytes
from webauthn.helpers.structs import PublicKeyCredentialDescriptor

from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.lib.models import SerializerModel
from authentik.stages.authenticator.models import Device

UNKNOWN_DEVICE_TYPE_AAGUID = "00000000-0000-0000-0000-000000000000"


class UserVerification(models.TextChoices):
    """The degree to which the Relying Party wishes to verify a user's identity.

    Members:
        `REQUIRED`: User verification must occur
        `PREFERRED`: User verification would be great, but if not that's okay too
        `DISCOURAGED`: User verification should not occur, but it's okay if it does

    https://www.w3.org/TR/webauthn-2/#enumdef-userverificationrequirement
    """

    REQUIRED = "required"
    PREFERRED = "preferred"
    DISCOURAGED = "discouraged"


class ResidentKeyRequirement(models.TextChoices):
    """The Relying Party's preference for the authenticator to create a dedicated "client-side"
    credential for it. Requiring an authenticator to store a dedicated credential should not be
    done lightly due to the limited storage capacity of some types of authenticators.

    Members:
        `DISCOURAGED`: The authenticator should not create a dedicated credential
        `PREFERRED`: The authenticator can create and store a dedicated credential, but if it
            doesn't that's alright too
        `REQUIRED`: The authenticator MUST create a dedicated credential. If it cannot, the RP
            is prepared for an error to occur.

    https://www.w3.org/TR/webauthn-2/#enum-residentKeyRequirement
    """

    DISCOURAGED = "discouraged"
    PREFERRED = "preferred"
    REQUIRED = "required"


class AuthenticatorAttachment(models.TextChoices):
    """How an authenticator is connected to the client/browser.

    Members:
        `PLATFORM`: A non-removable authenticator, like TouchID or Windows Hello
        `CROSS_PLATFORM`: A "roaming" authenticator, like a YubiKey

    https://www.w3.org/TR/webauthn-2/#enumdef-authenticatorattachment
    """

    PLATFORM = "platform"
    CROSS_PLATFORM = "cross-platform"


class AuthenticatorWebAuthnStage(ConfigurableStage, FriendlyNamedStage, Stage):
    """Stage to enroll WebAuthn-based authenticators."""

    user_verification = models.TextField(
        choices=UserVerification.choices,
        default=UserVerification.PREFERRED,
    )
    resident_key_requirement = models.TextField(
        choices=ResidentKeyRequirement.choices,
        default=ResidentKeyRequirement.PREFERRED,
    )
    authenticator_attachment = models.TextField(  # noqa: DJ001
        choices=AuthenticatorAttachment.choices, default=None, null=True
    )

    device_type_restrictions = models.ManyToManyField("WebAuthnDeviceType", blank=True)

    @property
    def serializer(self) -> type[BaseSerializer]:
        from authentik.stages.authenticator_webauthn.api.stages import (
            AuthenticatorWebAuthnStageSerializer,
        )

        return AuthenticatorWebAuthnStageSerializer

    @property
    def view(self) -> type[View]:
        from authentik.stages.authenticator_webauthn.stage import AuthenticatorWebAuthnStageView

        return AuthenticatorWebAuthnStageView

    @property
    def component(self) -> str:
        return "ak-stage-authenticator-webauthn-form"

    def ui_user_settings(self) -> UserSettingSerializer | None:
        return UserSettingSerializer(
            data={
                "title": self.friendly_name or str(self._meta.verbose_name),
                "component": "ak-user-settings-authenticator-webauthn",
            }
        )

    def __str__(self) -> str:
        return f"WebAuthn Authenticator Setup Stage {self.name}"

    class Meta:
        verbose_name = _("WebAuthn Authenticator Setup Stage")
        verbose_name_plural = _("WebAuthn Authenticator Setup Stages")


class WebAuthnDevice(SerializerModel, Device):
    """WebAuthn Device for a single user"""

    user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)

    name = models.TextField(max_length=200)
    credential_id = models.TextField(unique=True)
    public_key = models.TextField()
    sign_count = models.IntegerField(default=0)
    rp_id = models.CharField(max_length=253)

    created_on = models.DateTimeField(auto_now_add=True)
    last_t = models.DateTimeField(default=now)

    aaguid = models.TextField(default=UNKNOWN_DEVICE_TYPE_AAGUID)
    device_type = models.ForeignKey(
        "WebAuthnDeviceType", on_delete=models.SET_DEFAULT, null=True, default=None
    )

    @property
    def descriptor(self) -> PublicKeyCredentialDescriptor:
        """Get a publickeydescriptor for this device"""
        return PublicKeyCredentialDescriptor(id=base64url_to_bytes(self.credential_id))

    def set_sign_count(self, sign_count: int) -> None:
        """Set the sign_count and update the last_t datetime."""
        self.sign_count = sign_count
        self.last_t = now()
        self.save()

    @property
    def serializer(self) -> Serializer:
        from authentik.stages.authenticator_webauthn.api.devices import WebAuthnDeviceSerializer

        return WebAuthnDeviceSerializer

    def __str__(self):
        return str(self.name) or str(self.user_id)

    class Meta:
        verbose_name = _("WebAuthn Device")
        verbose_name_plural = _("WebAuthn Devices")


class WebAuthnDeviceType(SerializerModel):
    """WebAuthn device type, used to restrict which device types are allowed"""

    aaguid = models.UUIDField(primary_key=True, unique=True)

    description = models.TextField()
    icon = models.TextField(null=True)

    @property
    def serializer(self) -> Serializer:
        from authentik.stages.authenticator_webauthn.api.device_types import (
            WebAuthnDeviceTypeSerializer,
        )

        return WebAuthnDeviceTypeSerializer

    class Meta:
        verbose_name = _("WebAuthn Device type")
        verbose_name_plural = _("WebAuthn Device types")

    def __str__(self) -> str:
        return f"WebAuthn device type {self.description} ({self.aaguid})"
