"""RBAC management endpoints: roles, permissions catalog, user role assignment."""

from collections import defaultdict

from django.core.cache import cache
from django.db.models import Count
from django.shortcuts import get_object_or_404
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from .models import Permission, Role, User, UserRole
from .permissions import IsRbacAdmin
from .rbac_serializers import (
    AdminUserSerializer,
    AssignRolesSerializer,
    PermissionSerializer,
    RoleSerializer,
)


def _invalidate_role_users(role):
    """Bust the cached permission set of every user holding this role."""
    user_ids = role.user_assignments.values_list("user_id", flat=True)
    cache.delete_many([User.rbac_cache_key(uid) for uid in user_ids])


class PermissionCatalogView(APIView):
    """Lists all menu permissions, grouped by domain. Read-only, any auth user."""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        perms = Permission.objects.all()
        grouped: dict[str, list] = defaultdict(list)
        for p in perms:
            grouped[p.domain].append(PermissionSerializer(p).data)
        return Response({
            "permissions": PermissionSerializer(perms, many=True).data,
            "by_domain": grouped,
        })


class RoleViewSet(viewsets.ModelViewSet):
    """CRUD for Role. Admin-only writes; reads for any authenticated user
    (frontend needs role names to render badges/assignment UI)."""
    queryset = Role.objects.prefetch_related("permissions").annotate(
        user_count_anno=Count("user_assignments")
    )
    serializer_class = RoleSerializer

    def get_permissions(self):
        if self.action in ("list", "retrieve"):
            return [IsAuthenticated()]
        return [IsRbacAdmin()]

    def destroy(self, request, *args, **kwargs):
        role = self.get_object()
        if role.is_system:
            return Response(
                {"detail": "System role cannot be deleted."},
                status=status.HTTP_400_BAD_REQUEST,
            )
        _invalidate_role_users(role)
        return super().destroy(request, *args, **kwargs)

    @action(detail=True, methods=["post"], url_path="permissions")
    def set_permissions(self, request, pk=None):
        """Replace role.permissions with the given list of permission IDs."""
        role = self.get_object()
        ids = request.data.get("permission_ids", [])
        perms = Permission.objects.filter(id__in=ids)
        role.permissions.set(perms)
        _invalidate_role_users(role)
        return Response(RoleSerializer(role).data)


class AdminUserViewSet(viewsets.ReadOnlyModelViewSet):
    """Admin user list for the Users settings page (roles attached)."""
    queryset = User.objects.prefetch_related("user_roles__role").all().order_by("email")
    serializer_class = AdminUserSerializer
    permission_classes = [IsRbacAdmin]

    @action(detail=True, methods=["post"], url_path="roles")
    def assign_roles(self, request, pk=None):
        """Replace user's roles with the given list of role IDs."""
        user = self.get_object()
        serializer = AssignRolesSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        role_ids = serializer.validated_data["role_ids"]
        roles = Role.objects.filter(id__in=role_ids)

        UserRole.objects.filter(user=user).exclude(role__in=roles).delete()
        for role in roles:
            UserRole.objects.get_or_create(
                user=user, role=role,
                defaults={"assigned_by": request.user},
            )
        cache.delete(User.rbac_cache_key(user.pk))
        user = User.objects.prefetch_related("user_roles__role").get(pk=user.pk)
        return Response(AdminUserSerializer(user).data)
