"""Serializers for RBAC endpoints."""

from rest_framework import serializers

from .authorization import registry
from .models import Invitation, Permission, Role, User, UserRole


def validate_role_permission_set(permissions):
    """Reject an invalid v2 capability set on a role (plan section 8):
    unknown/wildcard codes and missing prerequisites. Legacy (`legacy.*` /
    uncoded) rows pass through unchanged during migration."""
    v2_codes = [
        p.code for p in permissions
        if p.code and not p.code.startswith("legacy.")
    ]
    problems = registry.validate_role_capability_codes(v2_codes)
    if problems:
        raise serializers.ValidationError(problems)


class PermissionSerializer(serializers.ModelSerializer):
    codename = serializers.CharField(read_only=True)

    class Meta:
        model = Permission
        fields = [
            "id", "domain", "action", "label", "codename",
            "code", "module", "resource", "description", "risk_level",
        ]
        read_only_fields = fields


class RoleReferenceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Role
        fields = ["id", "name", "slug"]
        read_only_fields = fields


class RoleSerializer(serializers.ModelSerializer):
    permissions = PermissionSerializer(many=True, read_only=True)
    permission_ids = serializers.PrimaryKeyRelatedField(
        queryset=Permission.objects.all(),
        many=True, write_only=True, required=False, source="permissions",
    )
    manager_role = RoleReferenceSerializer(read_only=True)
    manager_role_id = serializers.PrimaryKeyRelatedField(
        queryset=Role.objects.all(),
        source="manager_role",
        write_only=True,
        required=False,
        allow_null=True,
    )
    user_count = serializers.SerializerMethodField()

    class Meta:
        model = Role
        fields = [
            "id", "name", "slug", "description", "is_system",
            "manager_role", "manager_role_id",
            "permissions", "permission_ids", "user_count",
            "created_at", "updated_at",
        ]
        read_only_fields = ["id", "is_system", "created_at", "updated_at"]

    def get_user_count(self, obj) -> int:
        anno = getattr(obj, "user_count_anno", None)
        if anno is not None:
            return anno
        return obj.user_assignments.count()

    def validate_permission_ids(self, permissions):
        validate_role_permission_set(permissions)
        return permissions

    def validate_manager_role_id(self, manager_role):
        role = self.instance
        if manager_role is None or role is None:
            return manager_role
        if manager_role.pk == role.pk:
            raise serializers.ValidationError("A role cannot manage itself.")

        current = manager_role
        visited = set()
        while current is not None and current.pk not in visited:
            if current.pk == role.pk:
                raise serializers.ValidationError("Manager roles cannot form a circular reporting chain.")
            visited.add(current.pk)
            current = current.manager_role
        return manager_role


class AdminCreateUserSerializer(serializers.Serializer):
    """Direct user creation from the admin Users page (no invite email)."""
    email = serializers.EmailField()
    password = serializers.CharField(min_length=8, write_only=True)
    first_name = serializers.CharField(required=False, allow_blank=True, default="")
    last_name = serializers.CharField(required=False, allow_blank=True, default="")
    role_ids = serializers.ListField(
        child=serializers.IntegerField(), required=False, default=list,
    )

    def validate_email(self, value):
        email = value.strip().lower()
        if User.objects.filter(email__iexact=email).exists():
            raise serializers.ValidationError("A user with this email already exists.")
        return email

    def validate_role_ids(self, value):
        missing = set(value) - set(
            Role.objects.filter(id__in=value).values_list("id", flat=True)
        )
        if missing:
            raise serializers.ValidationError(f"Unknown role ids: {sorted(missing)}")
        return value


class AdminUserSerializer(serializers.ModelSerializer):
    """User shape for the admin Users page (with roles attached)."""
    full_name = serializers.SerializerMethodField()
    roles = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = [
            "id", "email", "username", "first_name", "last_name", "full_name",
            "avatar", "is_active", "is_staff", "is_superuser",
            "last_login", "date_joined", "roles",
        ]
        read_only_fields = fields

    def get_full_name(self, obj) -> str:
        return obj.get_full_name() or obj.username

    def get_roles(self, obj) -> list:
        # Reads the prefetched user_roles__role (see AdminUserViewSet queryset).
        return [
            {"id": ur.role.id, "slug": ur.role.slug, "name": ur.role.name}
            for ur in obj.user_roles.all()
        ]


class AssignRolesSerializer(serializers.Serializer):
    role_ids = serializers.ListField(
        child=serializers.IntegerField(), allow_empty=True
    )


class InvitationSerializer(serializers.ModelSerializer):
    role = serializers.PrimaryKeyRelatedField(read_only=True)
    role_id = serializers.PrimaryKeyRelatedField(
        queryset=Role.objects.all(), source="role", write_only=True, required=False, allow_null=True,
    )
    role_name = serializers.CharField(source="role.name", read_only=True, default=None)
    invited_by_email = serializers.CharField(source="invited_by.email", read_only=True, default=None)
    link = serializers.SerializerMethodField()

    class Meta:
        model = Invitation
        fields = [
            "id", "email", "role", "role_id", "role_name", "status",
            "invited_by", "invited_by_email", "expires_at", "created_at", "accepted_at", "link",
        ]
        read_only_fields = [
            "id", "role", "status", "invited_by", "invited_by_email",
            "expires_at", "created_at", "accepted_at", "link",
        ]

    def get_link(self, obj) -> str:
        from django.conf import settings
        return f"{settings.FRONTEND_URL.rstrip('/')}/invite/accept?token={obj.token}"


class InvitationAcceptSerializer(serializers.Serializer):
    token = serializers.CharField()
    password = serializers.CharField(required=False, allow_blank=True)
    first_name = serializers.CharField(required=False, allow_blank=True)
    last_name = serializers.CharField(required=False, allow_blank=True)
