"""
Administracion de las claves de API.

La clave ya no se guarda en claro, asi que el admin no puede mostrarla: solo
enseña el prefijo, que sirve para identificarla. Al crear una clave nueva se
genera el secreto y se muestra UNA sola vez en un mensaje; si se pierde, hay
que rotarla.
"""

from django.contrib import admin, messages

from keys.models.api_keys import ApiKeys


@admin.register(ApiKeys)
class ApiKeysAdmin(admin.ModelAdmin):
    # La clase se llamaba `RolesAdmin`, copiada del admin de authentication.
    list_display = (
        "account",
        "key_prefix_corto",
        "estado",
        "alcances",
        "last_used_at",
        "created_at",
    )
    list_filter = ("active", "account")
    search_fields = ("account__name", "key_prefix")
    readonly_fields = ("key_prefix", "last_used_at", "created_at", "revoked_at")
    # `key_hash` no aparece: mostrarlo no sirve para nada y solo aumenta las
    # posibilidades de que acabe copiado en algun sitio.
    fields = (
        "account",
        "active",
        "scopes",
        "allowed_senders",
        "key_prefix",
        "last_used_at",
        "created_at",
        "revoked_at",
    )
    actions = ("revocar_claves", "rotar_claves")

    @admin.display(description="Prefijo")
    def key_prefix_corto(self, obj) -> str:
        return f"{obj.key_prefix[:12]}…" if obj.key_prefix else "—"

    @admin.display(description="Activa", boolean=True)
    def estado(self, obj) -> bool:
        return obj.is_usable

    @admin.display(description="Alcances")
    def alcances(self, obj) -> str:
        # Sin alcances la clave autentica pero no puede hacer nada, y eso hay
        # que verlo de un vistazo en el listado.
        return ", ".join(obj.scopes) if obj.scopes else "— ninguno —"

    def save_model(self, request, obj, form, change):
        if change:
            super().save_model(request, obj, form, change)
            return

        # Alta: se genera la clave y se muestra una unica vez.
        registro, clave = ApiKeys.issue(
            obj.account,
            active=obj.active,
            # Lo que se haya escrito en el formulario; `issue` pone los
            # alcances por defecto si el campo viene vacio... salvo que se
            # haya dejado vacio a proposito, que es una lista, no None.
            scopes=obj.scopes if obj.scopes is not None else None,
            allowed_senders=obj.allowed_senders,
        )
        obj.pk = registro.pk
        self.message_user(
            request,
            f"Clave creada para «{registro.account.name}». Cópiala ahora, no se "
            f"volverá a mostrar:  {clave}",
            messages.WARNING,
        )

    @admin.action(description="Revocar las claves seleccionadas")
    def revocar_claves(self, request, queryset):
        # Se recorre en lugar de hacer un `update()` masivo: `revoke()` ademas
        # borra los tokens de acceso vivos de la clave. Con un `update()` la
        # fila quedaba revocada pero los tokens ya emitidos seguian en la
        # tabla.
        actualizadas = 0
        for registro in queryset.filter(revoked_at__isnull=True):
            registro.revoke()
            actualizadas += 1
        self.message_user(
            request,
            f"{actualizadas} clave(s) revocada(s). Dejan de funcionar en la "
            "siguiente petición.",
            messages.WARNING,
        )

    @admin.action(description="Rotar las claves seleccionadas")
    def rotar_claves(self, request, queryset):
        for registro in queryset:
            clave = registro.rotate()
            self.message_user(
                request,
                f"Clave nueva para «{registro.account.name}», cópiala ahora:  {clave}",
                messages.WARNING,
            )
