from uuid import UUID

from rest_framework import generics, status, serializers, viewsets
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from rest_framework_simplejwt.tokens import RefreshToken

from django.contrib.auth import get_user_model
from drf_spectacular.utils import (
    extend_schema,
    extend_schema_view,
    OpenApiParameter,
    OpenApiResponse,
)

from users.serializers import (
    AuditLogDetailSerializer,
    AuditLogListSerializer,
    CustomTokenObtainPairSerializer,
    CustomTokenRefreshSerializer,
    UserSerializer,
    UserCreateSerializer,
    UserUpdateSerializer,
    UserAdminUpdateSerializer,
)
from users.permissions import IsAdminUserRole, is_admin_user
from .models import AuditLog

User = get_user_model()


def _filter_logs_by_staff(queryset, request):
    staff_id = request.query_params.get("staff")
    if not staff_id:
        return queryset

    try:
        UUID(str(staff_id))
    except ValueError as exc:
        raise ValidationError({"staff": "Enter a valid staff user ID."}) from exc

    return queryset.filter(user_id=staff_id)


@extend_schema_view(
    post=extend_schema(
        tags=["Authentication"],
        summary="Log in and retrieve JWT Tokens",
        description="""
        Submit a valid email and password to obtain a pair of JSON Web Tokens (`access` and `refresh`).
        The payload generated will securely embed the `user` profile JSON object directly inside the root API response.
        """,
        responses={
            200: OpenApiResponse(
                description="Successfully assigned JWT connection.",
                response=CustomTokenObtainPairSerializer,
            )
        },
    )
)
class CustomTokenObtainPairView(TokenObtainPairView):
    """
    Login View. Takes email and password and returns access/refresh tokens
    along with the user profile injected via the CustomTokenObtainPairSerializer.
    """

    permission_classes = (AllowAny,)
    authentication_classes = []
    serializer_class = CustomTokenObtainPairSerializer


class CustomTokenRefreshView(TokenRefreshView):
    serializer_class = CustomTokenRefreshSerializer


@extend_schema_view(
    post=extend_schema(
        tags=["Authentication"],
        summary="Register a new User Account",
        description="Allows anyone to create an account, complete with mandatory password validation.",
    )
)
class UserRegistrationView(generics.CreateAPIView):
    """
    Registration View. Allows anyone to create an account.
    """

    queryset = User.objects.all()
    permission_classes = (AllowAny,)
    serializer_class = UserCreateSerializer


@extend_schema_view(
    get=extend_schema(
        tags=["Users"],
        summary="Fetch Authorized Profile",
        description="Retrieves the detailed native profile document for the currently logged in user context.",
    ),
    patch=extend_schema(
        tags=["Users"],
        summary="Update Native Profile via Patch",
        description="""
        Partially updates the user profile context state mappings dynamically.
        You can use this mutation to securely submit password changes too!
        """,
    ),
    put=extend_schema(
        exclude=True,  # Prevent standard PUT from crowding Swagger interface
    ),
)
class UserProfileView(generics.RetrieveUpdateAPIView):
    """
    Profile View. Requires Authentication.
    GET /users/me/ fetches the currently logged in user.
    PATCH /users/me/ updates their profile or password.
    """

    permission_classes = (IsAuthenticated,)

    def get_serializer_class(self):
        if self.request.method in ["PUT", "PATCH"]:
            return UserUpdateSerializer
        return UserSerializer

    def get_object(self):
        # Always return the actively logged-in user, discarding the PK from URLs.
        return self.request.user


# We create a dummy serializer just so Swagger natively knows what Logout expects functionally
class LogoutSerializer(serializers.Serializer):
    refresh = serializers.CharField(
        help_text="The valid JWT refresh token to destroy natively."
    )


@extend_schema_view(
    post=extend_schema(
        tags=["Authentication"],
        summary="Log Out (Destroy Refresh Token)",
        description="""
        Submits an active refresh token and definitively invalidates it natively on the authorization backend DB.
        The user client must obtain new credentials entirely afterward.
        """,
        request=LogoutSerializer,
        responses={
            205: OpenApiResponse(
                description="Successfully cleared state and destroyed tokens natively."
            ),
            400: OpenApiResponse(description="Token missing, rejected, or expired."),
        },
    )
)
class LogoutView(APIView):
    """
    Logout View.
    Accepts the refresh token and optionally blacklists it so it cannot be used again.
    """

    permission_classes = (IsAuthenticated,)

    def post(self, request):
        try:
            refresh_token = request.data.get("refresh")
            if not refresh_token:
                return Response(
                    {"error": "Refresh token is required."},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            token = RefreshToken(refresh_token)
            token.blacklist()

            return Response(status=status.HTTP_205_RESET_CONTENT)
        except Exception:
            return Response(
                {"error": "Invalid or expired token."},
                status=status.HTTP_400_BAD_REQUEST,
            )


@extend_schema_view(
    get=extend_schema(
        tags=["Users"],
        summary="List users with role-based scope",
        description=(
            "Admins can list all users (excluding superusers). "
            "Staff users can list only users associated with their own checkpoint."
        ),
    )
)
class UserListView(generics.ListAPIView):
    """
    List View.
    - Admin users can list all non-superusers.
    - Staff users can list only users from their own assigned checkpoint.
    """

    permission_classes = (IsAuthenticated,)
    serializer_class = UserSerializer
    filterset_fields = ("role", "status", "checkpoint")
    search_fields = ("name", "email", "designation")
    ordering_fields = ("created_at", "name", "email")
    ordering = ("-created_at",)

    def get_queryset(self):
        queryset = User.objects.filter(is_superuser=False).select_related("checkpoint")
        current_user = self.request.user

        if is_admin_user(current_user):
            return queryset

        user_checkpoint = getattr(current_user, "checkpoint", None)
        checkpoint_id = self.request.query_params.get("checkpoint")

        if not user_checkpoint:
            if checkpoint_id:
                raise ValidationError(
                    {
                        "checkpoint": (
                            "You do not have an assigned checkpoint, so checkpoint "
                            "filtering is not available."
                        )
                    }
                )
            return queryset.filter(id=current_user.id)

        if checkpoint_id and str(checkpoint_id) != str(user_checkpoint.id):
            raise ValidationError(
                {
                    "checkpoint": (
                        "You can only filter users within your assigned checkpoint."
                    )
                }
            )

        return queryset.filter(checkpoint=user_checkpoint)


@extend_schema_view(
    get=extend_schema(
        tags=["Users"],
        summary="Fetch a user as admin",
        description="Allows admins to retrieve a non-superuser account by ID.",
    ),
    patch=extend_schema(
        tags=["Users"],
        summary="Update a user as admin",
        description=(
            "Allows admins to update managed user fields. Setting status to "
            "`inactive` or `suspended` also disables API authentication for the user."
        ),
    ),
)
class UserDetailView(generics.RetrieveUpdateAPIView):
    """
    Admin-only detail view for managing non-superuser accounts.
    """

    permission_classes = (IsAdminUserRole,)
    serializer_class = UserAdminUpdateSerializer
    queryset = User.objects.filter(is_superuser=False).select_related("checkpoint")
    http_method_names = ["get", "patch", "head", "options"]


@extend_schema_view(
    list=extend_schema(
        tags=["Audit Logs"],
        summary="List Audit Logs",
        description="Allows admins to view detailed technical audit logs.",
        parameters=[
            OpenApiParameter(
                name="staff",
                type=str,
                location=OpenApiParameter.QUERY,
                description="Filter logs by staff user ID.",
            )
        ],
    ),
    retrieve=extend_schema(
        tags=["Audit Logs"],
        summary="Retrieve Audit Log Detail",
        description="Allows admins to view technical details for a specific audit log.",
    ),
)
class AuditLogViewSet(viewsets.ReadOnlyModelViewSet):
    """
    ViewSet for viewing detailed audit logs.
    Only accessible by Admin role users.
    """

    permission_classes = [IsAdminUserRole]
    queryset = AuditLog.objects.all().select_related("user")

    filterset_fields = ("action", "method", "status_code", "user", "target_model")
    search_fields = (
        "path",
        "action_description",
        "target_model",
        "user__email",
        "user__name",
    )
    ordering_fields = ("created_at", "status_code", "action")
    ordering = ("-created_at",)

    def get_queryset(self):
        return _filter_logs_by_staff(super().get_queryset(), self.request)

    def get_serializer_class(self):
        if self.action == "retrieve":
            return AuditLogDetailSerializer
        return AuditLogListSerializer
