"""Admin-only user / role / permission management — exposed over the API
instead of the Django admin site. All endpoints require role == ADMIN."""
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from drf_spectacular.utils import extend_schema
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.views import APIView

from core.permissions import CanManageRoles, DjangoModelPermissionsWithView, IsAdmin
from core.responses import error_response, success_response
from core.roles import DEFAULT_ROLE_PERMISSIONS, MANAGED_ROLES, is_admin_role

from .admin_serializers import (
    AdminUserSerializer,
    PermissionKeysSerializer,
    PermissionSerializer,
    RoleCreateSerializer,
    SetRoleSerializer,
)
from .groups_serializers import GroupSerializer, UserGroupsSerializer, AssignGroupsSerializer

User = get_user_model()


def _perm_key(perm):
    return f"{perm.content_type.app_label}.{perm.codename}"


def _is_admin(user):
    return bool(user and (getattr(user, "role", None) == "ADMIN" or user.is_superuser))


class AdminUserViewSet(viewsets.ModelViewSet):
    """CRUD over user accounts. /api/v1/users/admin/users/

    Access is permission-driven (Django model perms on users.user): an admin can
    grant a non-admin the users.view/add/change/delete_user permissions and they
    will then see and use this module. Role assignment and per-user permission
    grants stay ADMIN-only, and non-admins can never create or touch ADMIN users
    (prevents privilege escalation)."""

    queryset = User.objects.all().order_by("-date_joined")
    serializer_class = AdminUserSerializer
    permission_classes = [DjangoModelPermissionsWithView]

    def get_permissions(self):
        # Escalation-sensitive actions are admin-only.
        if self.action in {"set_role", "permissions"}:
            return [IsAdmin()]
        return super().get_permissions()

    def get_serializer_class(self):
        # Mask phone in the bulk user list; full value in detail/update.
        if self.action == "list":
            from .admin_serializers import AdminUserListSerializer
            return AdminUserListSerializer
        return AdminUserSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        role = self.request.query_params.get("role")
        if role:
            qs = qs.filter(role=role)
        is_active = self.request.query_params.get("is_active")
        if is_active is not None:
            if is_active.lower() in ("true", "1", "active"):
                qs = qs.filter(is_active=True)
            elif is_active.lower() in ("false", "0", "inactive"):
                qs = qs.filter(is_active=False)
        search = self.request.query_params.get("search")
        if search:
            from django.db.models import Q

            qs = qs.filter(
                Q(email__icontains=search) | Q(full_name__icontains=search)
            )
        return qs

    def list(self, request, *args, **kwargs):
        page = self.paginate_queryset(self.filter_queryset(self.get_queryset()))
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        if serializer.validated_data.get("role") == "ADMIN" and not _is_admin(request.user):
            return error_response("Only an administrator can create admin users.", status_code=403)
        serializer.save()
        return success_response(serializer.data, "User created", status_code=201)

    def retrieve(self, request, *args, **kwargs):
        return success_response(self.get_serializer(self.get_object()).data)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop("partial", False)
        target = self.get_object()
        admin = _is_admin(request.user)
        # Non-admins may not edit admin accounts, nor promote anyone to ADMIN.
        if not admin and (target.role == "ADMIN" or request.data.get("role") == "ADMIN"):
            return error_response("Only an administrator can modify admin users or roles.", status_code=403)
        serializer = self.get_serializer(target, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return success_response(serializer.data, "User updated")

    def destroy(self, request, *args, **kwargs):
        user = self.get_object()
        if user == request.user:
            return error_response("You cannot delete your own account.", status_code=400)
        if user.role == "ADMIN" and not _is_admin(request.user):
            return error_response("Only an administrator can delete admin users.", status_code=403)
        user.delete()  # post_delete signal removes it from auth_mcp too
        return success_response(message="User deleted")

    @extend_schema(request=SetRoleSerializer, responses=AdminUserSerializer)
    @action(detail=True, methods=["post"], url_path="set-role")
    def set_role(self, request, pk=None):
        user = self.get_object()
        serializer = SetRoleSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user.role = serializer.validated_data["role"]
        user.save()  # signal re-syncs the role group + MCP role
        return success_response(
            AdminUserSerializer(user).data, f"Role set to {user.role}"
        )

    @action(detail=True, methods=["get", "post"], url_path="lock")
    def lock(self, request, pk=None):
        """Manage a user's login lock (the failed-login lockout).

        GET  -> {is_locked, failed_attempts, locked_at}
        POST {"locked": true}  -> lock the account
        POST {"locked": false} -> unlock (clears failed attempts)
        """
        from django.utils import timezone

        from apps.authentication.models import LoginAttempt

        user = self.get_object()
        attempt, _ = LoginAttempt.objects.get_or_create(user=user)

        if request.method == "GET":
            return success_response({
                "is_locked": attempt.is_currently_locked(),
                "failed_attempts": attempt.failed_attempts,
                "locked_at": attempt.locked_at,
            })

        # POST — lock/unlock. You can't lock yourself out.
        if user == request.user:
            return error_response("You cannot lock or unlock your own account.", status_code=400)

        locked = bool(request.data.get("locked"))
        if locked:
            attempt.failed_attempts = LoginAttempt.MAX_FAILED_ATTEMPTS
            attempt.is_locked = True
            attempt.locked_at = timezone.now()
            attempt.save(update_fields=["failed_attempts", "is_locked", "locked_at"])
            return success_response({"is_locked": True}, f"{user.email} locked")

        attempt.reset()
        return success_response({"is_locked": False}, f"{user.email} unlocked")

    @extend_schema(request=PermissionKeysSerializer, responses=None)
    @action(detail=True, methods=["get", "put"], url_path="permissions")
    def permissions(self, request, pk=None):
        """GET = this user's extra (per-user) permissions + effective set.
        PUT = replace the user's extra permissions."""
        user = self.get_object()
        if request.method == "GET":
            return success_response({
                "role": user.role,
                "extra_permissions": sorted(
                    _perm_key(p) for p in user.user_permissions.select_related("content_type")
                ),
                "effective_permissions": sorted(user.get_all_permissions()),
            })
        serializer = PermissionKeysSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user.user_permissions.set(serializer.context["resolved_permissions"])
        user = User.objects.get(pk=user.pk)  # drop the perm cache
        return success_response({
            "extra_permissions": sorted(
                _perm_key(p) for p in user.user_permissions.select_related("content_type")
            ),
            "effective_permissions": sorted(user.get_all_permissions()),
        }, "User permissions updated")

    @extend_schema(request=AssignGroupsSerializer, responses=UserGroupsSerializer)
    @action(detail=True, methods=["get", "patch"], url_path="groups")
    def groups(self, request, pk=None):
        """GET /api/v1/users/admin/users/{user_id}/groups/
        Returns user's assigned groups and available groups.
        
        PATCH /api/v1/users/admin/users/{user_id}/groups/
        Update user's group assignments."""
        user = self.get_object()
        
        if request.method == "GET":
            assigned_groups = user.groups.all()
            available_groups = Group.objects.exclude(name="ADMIN")
            
            response_data = {
                'user_id': user.id,
                'user_name': user.full_name or user.email,
                'email': user.email,
                'assigned_groups': GroupSerializer(assigned_groups, many=True).data,
                'available_groups': GroupSerializer(available_groups, many=True).data,
            }
            return success_response(response_data)
        
        elif request.method == "PATCH":
            serializer = AssignGroupsSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            
            group_ids = serializer.validated_data['group_ids']
            groups = Group.objects.filter(id__in=group_ids)
            user.groups.set(groups)
            
            return success_response({'message': 'Groups updated successfully.'}, "User groups updated")


def _ordered_role_groups():
    """All role groups, seed roles first (in their canonical order), then any
    custom roles alphabetically."""
    groups = list(Group.objects.all())
    order = {name: i for i, name in enumerate(MANAGED_ROLES)}
    return sorted(groups, key=lambda g: (order.get(g.name, len(order)), g.name))


class RoleListView(APIView):
    """GET  /api/v1/users/admin/roles/ — list every role (Group) dynamically.
    POST /api/v1/users/admin/roles/ — create a new role (Group)."""

    permission_classes = [CanManageRoles]

    def get(self, request):
        data = []
        for group in _ordered_role_groups():
            role = group.name
            perms = sorted(
                _perm_key(p) for p in group.permissions.select_related("content_type")
            )
            users = User.objects.filter(role=role).values("id", "email", "username", "full_name", "is_active").order_by("-date_joined")[:100]  # limit for performance
            user_count = len(users)
            data.append({
                "role": role,
                "is_admin": is_admin_role(role),
                "is_seed": role in MANAGED_ROLES,
                "user_count": user_count,
                "deletable": not is_admin_role(role) and user_count == 0,
                "permission_count": (
                    Permission.objects.count() if is_admin_role(role) else len(perms)
                ),
                "permissions": ["*"] if is_admin_role(role) else perms,
                "users": users,
            })
        return success_response(data)

    @extend_schema(request=RoleCreateSerializer, responses=None)
    def post(self, request):
        serializer = RoleCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        name = serializer.validated_data["name"]
        group = Group.objects.create(name=name)
        group.permissions.set(serializer.context.get("resolved_permissions", []))
        return success_response({
            "role": name,
            "permissions": sorted(
                _perm_key(p) for p in group.permissions.select_related("content_type")
            ),
        }, f"Role {name} created", status_code=201)


class RoleDetailView(APIView):
    """DELETE /api/v1/users/admin/roles/<role>/ — remove a role (Group).
    Blocked for ADMIN and for any role that still has users assigned."""

    permission_classes = [CanManageRoles]

    def delete(self, request, role):
        group = Group.objects.filter(name=role).first()
        if not group:
            return error_response("Unknown role", status_code=404)
        if is_admin_role(role):
            return error_response("The ADMIN role cannot be deleted.", status_code=400)
        if role in MANAGED_ROLES:
            return error_response(
                f"'{role}' is a default role and cannot be deleted.", status_code=400
            )
        in_use = User.objects.filter(role=role).count()
        if in_use:
            return error_response(
                f"Cannot delete '{role}': {in_use} user(s) still have this role. "
                "Reassign them first.",
                status_code=400,
            )
        group.delete()
        return success_response(message=f"Role {role} deleted")


class RoleCloneView(APIView):
    """POST /api/v1/users/admin/roles/<role>/clone/ — duplicate a role (Group)
    with all its permissions. The clone is an independent CUSTOM role named
    "<role> Copy" (auto-incremented when taken) — fully editable and, unlike
    seed roles, deletable. ADMIN can never be cloned."""

    permission_classes = [CanManageRoles]

    def post(self, request, role):
        group = Group.objects.filter(name=role).first()
        if not group:
            return error_response("Unknown role", status_code=404)
        if is_admin_role(role):
            return error_response("The ADMIN role cannot be cloned.", status_code=400)

        base_name = f"{role} Copy"
        name, n = base_name, 2
        while Group.objects.filter(name=name).exists():
            name = f"{base_name} {n}"
            n += 1

        clone = Group.objects.create(name=name)
        clone.permissions.set(group.permissions.all())
        return success_response({
            "role": name,
            "cloned_from": role,
            "permissions": sorted(
                _perm_key(p) for p in clone.permissions.select_related("content_type")
            ),
        }, "Role cloned successfully.", status_code=201)


class RolePermissionsView(APIView):
    """GET/PUT /api/v1/users/admin/roles/<role>/permissions/ — manage a role's
    group permissions. Changing these affects every user with that role."""

    permission_classes = [CanManageRoles]

    def get(self, request, role):
        if not Group.objects.filter(name=role).exists():
            return error_response("Unknown role", status_code=404)
        if is_admin_role(role):
            return success_response({
                "role": role,
                "note": "Admins implicitly have all permissions.",
                "permissions": ["*"],
            })
        group, _ = Group.objects.get_or_create(name=role)
        return success_response({
            "role": role,
            "permissions": sorted(
                _perm_key(p) for p in group.permissions.select_related("content_type")
            ),
        })

    @extend_schema(request=PermissionKeysSerializer, responses=None)
    def put(self, request, role):
        if not Group.objects.filter(name=role).exists():
            return error_response("Unknown role", status_code=404)
        if is_admin_role(role):
            return error_response(
                "The ADMIN role's permissions cannot be edited.", status_code=400
            )
        serializer = PermissionKeysSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        group, _ = Group.objects.get_or_create(name=role)
        group.permissions.set(serializer.context["resolved_permissions"])
        return success_response({
            "role": role,
            "permissions": sorted(
                _perm_key(p) for p in group.permissions.select_related("content_type")
            ),
        }, f"Permissions for {role} updated")


class RoleResetView(APIView):
    """POST /api/v1/users/admin/roles/<role>/reset/ — restore a role's group
    permissions to the defaults defined in core.roles (custom roles -> empty)."""

    permission_classes = [CanManageRoles]

    def post(self, request, role):
        if not Group.objects.filter(name=role).exists() or is_admin_role(role):
            return error_response("Role cannot be reset", status_code=400)
        group, _ = Group.objects.get_or_create(name=role)
        perms = []
        for key in DEFAULT_ROLE_PERMISSIONS.get(role, []):
            app_label, codename = key.split(".", 1)
            perm = Permission.objects.filter(
                content_type__app_label=app_label, codename=codename
            ).first()
            if perm:
                perms.append(perm)
        group.permissions.set(perms)
        return success_response({
            "role": role,
            "permissions": sorted(_perm_key(p) for p in perms),
        }, f"{role} permissions reset to defaults")


class PermissionCatalogView(APIView):
    """GET /api/v1/users/admin/permissions/ — every assignable permission,
    so the frontend can render checkboxes."""

    permission_classes = [CanManageRoles]

    def get(self, request):
        qs = Permission.objects.select_related("content_type").order_by(
            "content_type__app_label", "content_type__model", "codename"
        )
        app = request.query_params.get("app")
        if app:
            qs = qs.filter(content_type__app_label=app)
        return success_response(PermissionSerializer(qs, many=True).data)
