"""common RBAC serializers"""

from django.apps import apps
from django.contrib.auth.models import Permission
from django.db.models import QuerySet
from django_filters.filters import ModelChoiceFilter
from django_filters.filterset import FilterSet
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
    CharField,
    ChoiceField,
    ListField,
    ReadOnlyField,
    SerializerMethodField,
)
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet

from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.lib.api import model_choices
from authentik.lib.validators import RequiredTogetherValidator
from authentik.rbac.models import Role


class PermissionSerializer(ModelSerializer):
    """Global permission"""

    app_label = ReadOnlyField(source="content_type.app_label")
    app_label_verbose = SerializerMethodField()
    model = ReadOnlyField(source="content_type.model")
    model_verbose = SerializerMethodField()

    def get_app_label_verbose(self, instance: Permission) -> str:
        """Human-readable app label"""
        try:
            return apps.get_app_config(instance.content_type.app_label).verbose_name
        except LookupError:
            return f"{instance.content_type.app_label}.{instance.content_type.model}"

    def get_model_verbose(self, instance: Permission) -> str:
        """Human-readable model name"""
        try:
            return apps.get_model(
                instance.content_type.app_label, instance.content_type.model
            )._meta.verbose_name
        except LookupError:
            return f"{instance.content_type.app_label}.{instance.content_type.model}"

    class Meta:
        model = Permission
        fields = [
            "id",
            "name",
            "codename",
            "model",
            "app_label",
            "app_label_verbose",
            "model_verbose",
        ]


class PermissionAssignResultSerializer(PassiveSerializer):
    """Result from assigning permissions to a user/role"""

    id = CharField()


class PermissionFilter(FilterSet):
    """Filter permissions"""

    role = ModelChoiceFilter(queryset=Role.objects.all(), method="filter_role")

    def filter_role(self, queryset: QuerySet, name, value: Role) -> QuerySet:
        """Filter permissions based on role"""
        return queryset.filter(rolemodelpermission__role=value)

    class Meta:
        model = Permission
        fields = [
            "codename",
            "content_type__model",
            "content_type__app_label",
            "role",
        ]


class RBACPermissionViewSet(ReadOnlyModelViewSet):
    """Read-only list of all permissions, filterable by model and app"""

    queryset = Permission.objects.none()
    serializer_class = PermissionSerializer
    ordering = ["name"]
    filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
    filterset_class = PermissionFilter
    permission_classes = [IsAuthenticated]
    search_fields = [
        "name",
        "codename",
        "content_type__model",
        "content_type__app_label",
    ]

    def get_queryset(self) -> QuerySet:
        return (
            Permission.objects.all()
            .select_related("content_type")
            .filter(
                content_type__app_label__startswith="authentik",
            )
        )


class PermissionAssignSerializer(PassiveSerializer):
    """Request to assign a new permission"""

    permissions = ListField(child=CharField())
    model = ChoiceField(choices=model_choices(), required=False)
    object_pk = CharField(required=False)

    validators = [RequiredTogetherValidator(fields=["model", "object_pk"])]

    def validate(self, attrs: dict) -> dict:
        model_instance = None
        # Check if we're setting an object-level perm or global
        model = attrs.get("model")
        object_pk = attrs.get("object_pk")
        if model and object_pk:
            model = apps.get_model(attrs["model"])
            model_instance = model.objects.filter(pk=attrs["object_pk"]).first()
        attrs["model_instance"] = model_instance
        if attrs.get("model"):
            return attrs
        permissions = attrs.get("permissions", [])
        if not all("." in perm for perm in permissions):
            raise ValidationError(
                {
                    "permissions": (
                        "When assigning global permissions, codename must be given as "
                        "app_label.codename"
                    )
                }
            )
        return attrs
