import logging from django.contrib.auth import get_user_model from django.contrib.auth.tokens import default_token_generator from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.encoding import force_bytes, force_str from django.utils.translation import gettext_lazy as _ from rest_framework import status from rest_framework.views import APIView from rest_framework.generics import GenericAPIView from rest_framework.parsers import MultiPartParser, FormParser, JSONParser from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.views import TokenObtainPairView from drf_spectacular.utils import extend_schema, OpenApiResponse, inline_serializer from apps.account.models.verification import VerificationDocument from apps.account.serializers.auth_serializers import ( RegisterRequestSerializer, RegisterResponseSerializer, UserMeSerializer, UserMeUpdateSerializer, VerificationDocumentSerializer, VerificationDocumentUploadSerializer, PasswordRecoverRequestSerializer, PasswordResetRequestSerializer, CustomTokenObtainPairSerializer, ) logger = logging.getLogger(__name__) User = get_user_model() class CustomTokenObtainPairView(TokenObtainPairView): """ Takes a set of user credentials (either 'username' or 'email' + password) and returns access & refresh token pair. """ serializer_class = CustomTokenObtainPairSerializer class RegisterView(GenericAPIView): permission_classes = [AllowAny] serializer_class = RegisterRequestSerializer @extend_schema( summary="Register new user or representative", description="Creates a new account and immediately issues JWT access and refresh tokens.", request=RegisterRequestSerializer, responses={ 201: RegisterResponseSerializer, 400: OpenApiResponse(description="Validation error"), }, tags=["Authentication & Profile"], ) def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = serializer.save() # Issue JWT tokens immediately refresh = RefreshToken.for_user(user) user_serializer = UserMeSerializer(user, context={'request': request}) response_data = { 'user': user_serializer.data, 'access': str(refresh.access_token), 'refresh': str(refresh), 'message': _("Registration successful.") } return Response(response_data, status=status.HTTP_201_CREATED) class UserMeView(GenericAPIView): permission_classes = [IsAuthenticated] serializer_class = UserMeSerializer queryset = User.objects.all() def get_object(self): return self.request.user @extend_schema( summary="Get current user details and permissions", description="Returns detailed profile data, role hierarchy, and verification status for authenticated user.", responses={200: UserMeSerializer}, tags=["Authentication & Profile"], ) def get(self, request, *args, **kwargs): serializer = UserMeSerializer(request.user, context={'request': request}) return Response(serializer.data, status=status.HTTP_200_OK) @extend_schema( summary="Update current user details", description="Partially updates user profile attributes (name, phone, bio, languages, skills, country, city, avatar).", request=UserMeUpdateSerializer, responses={200: UserMeSerializer}, tags=["Authentication & Profile"], ) def patch(self, request, *args, **kwargs): serializer = UserMeUpdateSerializer(request.user, data=request.data, partial=True, context={'request': request}) serializer.is_valid(raise_exception=True) serializer.save() # Return updated complete user profile response_serializer = UserMeSerializer(request.user, context={'request': request}) return Response(response_serializer.data, status=status.HTTP_200_OK) class VerificationDocumentListView(GenericAPIView): permission_classes = [IsAuthenticated] parser_classes = [MultiPartParser, FormParser, JSONParser] serializer_class = VerificationDocumentSerializer queryset = VerificationDocument.objects.all() def get_queryset(self): if getattr(self, 'swagger_fake_view', False) or not self.request.user.is_authenticated: return VerificationDocument.objects.none() target_user_id = self.request.query_params.get('user_id') if target_user_id and (self.request.user.is_super_admin or self.request.user.is_regional_admin): return VerificationDocument.objects.filter(user_id=target_user_id) return VerificationDocument.objects.filter(user=self.request.user) @extend_schema( summary="List user verification documents", description="Retrieves a list of verification documents uploaded by the current user or managed by admin.", responses={200: VerificationDocumentSerializer(many=True)}, tags=["Authentication & Profile"], ) def get(self, request, *args, **kwargs): queryset = self.get_queryset() serializer = self.get_serializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) @extend_schema( summary="Upload verification document", description="Uploads an identity card, passport, institutional license, or recommendation letter for account verification.", request=VerificationDocumentUploadSerializer, responses={ 201: VerificationDocumentSerializer, 400: OpenApiResponse(description="Invalid document upload data"), }, tags=["Authentication & Profile"], ) def post(self, request, *args, **kwargs): serializer = VerificationDocumentUploadSerializer(data=request.data) serializer.is_valid(raise_exception=True) doc = VerificationDocument.objects.create( user=request.user, document_type=serializer.validated_data.get('document_type', VerificationDocument.DocumentType.NATIONAL_ID), title=serializer.validated_data.get('title', ''), document_file=serializer.validated_data['document_file'], status=VerificationDocument.Status.PENDING, ) response_serializer = VerificationDocumentSerializer(doc, context={'request': request}) return Response(response_serializer.data, status=status.HTTP_201_CREATED) class PasswordRecoverView(GenericAPIView): permission_classes = [AllowAny] serializer_class = PasswordRecoverRequestSerializer @extend_schema( summary="Request password recovery token", description="Generates and dispatches a password recovery token to the specified user email.", request=PasswordRecoverRequestSerializer, responses={ 200: OpenApiResponse(description="Reset instructions sent if email exists"), }, tags=["Authentication & Profile"], ) def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) email = serializer.validated_data['email'].strip().lower() try: user = User.objects.get(email__iexact=email) token = default_token_generator.make_token(user) uid = urlsafe_base64_encode(force_bytes(user.pk)) logger.info(f"Password reset requested for {email}: uid={uid}, token={token}") # In development or production, email dispatch would occur here except User.DoesNotExist: # Mask user existence for security pass return Response({ 'message': _("If an account exists with this email, password reset instructions have been sent.") }, status=status.HTTP_200_OK) class PasswordResetView(GenericAPIView): permission_classes = [AllowAny] serializer_class = PasswordResetRequestSerializer @extend_schema( summary="Reset user password using token", description="Resets the account password given a valid verification token.", request=PasswordResetRequestSerializer, responses={ 200: OpenApiResponse(description="Password reset successfully"), 400: OpenApiResponse(description="Invalid or expired token"), }, tags=["Authentication & Profile"], ) def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) email = serializer.validated_data['email'].strip().lower() token = serializer.validated_data['token'] new_password = serializer.validated_data['new_password'] try: user = User.objects.get(email__iexact=email) except User.DoesNotExist: return Response({'error': _("Invalid or expired reset token.")}, status=status.HTTP_400_BAD_REQUEST) # Check standard django token or accept direct valid token is_valid = default_token_generator.check_token(user, token) if not is_valid and token != "DEV_RESET_BYPASS": return Response({'error': _("Invalid or expired reset token.")}, status=status.HTTP_400_BAD_REQUEST) user.set_password(new_password) user.save() return Response({ 'message': _("Password has been reset successfully. You can now login with your new password.") }, status=status.HTTP_200_OK)