from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_decode
from rest_framework import generics, permissions, status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.exceptions import TokenError
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.views import TokenObtainPairView
from djoser.utils import decode_uid

from .emails import send_password_reset_email, send_qr_code_email
from .serializers import (
    PasswordChangeSerializer,
    UserAccountSerializer,
    UserProfileUpdateSerializer,
    UserRegistrationSerializer,
)

User = get_user_model()


class CustomTokenCreateView(TokenObtainPairView):
    """
    Custom JWT Token view to set the refresh token in an HttpOnly cookie.
    """
    def post(self, request, *args, **kwargs):
        response = super().post(request, *args, **kwargs)
        
        if response.status_code == status.HTTP_200_OK:
            refresh_token = response.data.get("refresh")
            if refresh_token:
                response.data.pop("refresh")
                response.set_cookie(
                    key='refresh_token',
                    value=refresh_token,
                    httponly=True,
                    secure=True,
                    samesite='Lax'
                )
        return response


class LogoutView(APIView):
    """
    Déconnecte l'utilisateur :
    1. Blackliste le refresh token (lu depuis le cookie HttpOnly `refresh_token`)
       via rest_framework_simplejwt.token_blacklist — empêche tout futur refresh
       d'access token avec ce refresh token.
    2. Supprime le cookie `refresh_token`.

    POST /api/user/auth/logout/

    Permission: AllowAny — le logout doit marcher même avec un access token
    expiré côté client.

    Limitation: l'access token courant reste valide jusqu'à expiration naturelle
    (ACCESS_TOKEN_LIFETIME = 60 min). simplejwt ne propose pas de blacklist
    d'access tokens en standard.
    """
    permission_classes = [AllowAny]

    def post(self, request):
        refresh_token = request.COOKIES.get('refresh_token')

        if refresh_token:
            try:
                RefreshToken(refresh_token).blacklist()
            except TokenError:
                # Token déjà invalide / expiré / déjà blacklisté : on continue
                # quand même pour supprimer le cookie côté client.
                pass

        response = Response(
            {"detail": "Déconnexion réussie."},
            status=status.HTTP_200_OK,
        )
        response.delete_cookie(
            key='refresh_token',
            samesite='Lax',
        )
        return response


class UserRegistrationView(generics.CreateAPIView):
    """
    Endpoint pour l'inscription d'un nouvel utilisateur.
    Valide les données, crée l'utilisateur et déclenche l'envoi du mail de vérification via signal.
    """
    serializer_class = UserRegistrationSerializer
    permission_classes = [AllowAny]

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        
        return Response(
            {
                "detail": "Inscription réussie. Un email de vérification a été envoyé à votre adresse.",
                "user": {
                    "email": serializer.data.get("email"),
                    "first_name": serializer.data.get("first_name"),
                    "last_name": serializer.data.get("last_name"),
                }
            },
            status=status.HTTP_201_CREATED,
            headers=headers
        )


class EmailVerificationView(APIView):
    """
    Verify email after registration.
    Sets is_email_verified = True and sends the QR code email.
    """
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        uid = request.data.get("uid")
        token = request.data.get("token")

        if not uid or not token:
            return Response({"error": "UID et token sont requis."}, status=status.HTTP_400_BAD_REQUEST)

        try:
            user_id = decode_uid(uid)
            user = User.objects.get(pk=user_id)
        except (User.DoesNotExist, ValueError, TypeError):
            return Response({"error": "Utilisateur introuvable ou UID invalide."}, status=status.HTTP_400_BAD_REQUEST)

        if not default_token_generator.check_token(user, token):
            return Response({"error": "Token invalide ou expiré."}, status=status.HTTP_400_BAD_REQUEST)

        if user.is_email_verified:
            return Response({"message": "Email déjà vérifié."}, status=status.HTTP_200_OK)

        # Mark email as verified
        user.is_email_verified = True
        user.save(update_fields=["is_email_verified"])

        # Send QR Code via email
        send_qr_code_email(user)

        return Response({"status": "email_validated", "message": "Email vérifié. Votre QR code a été envoyé."}, status=status.HTTP_200_OK)


class UserAccountView(generics.RetrieveUpdateAPIView):
    """
    Dashboard API to get and update the current user's profile.
    """
    serializer_class = UserAccountSerializer
    permission_classes = [IsAuthenticated]

    def get_object(self):
        return self.request.user


class UpdateProfileView(generics.UpdateAPIView):
    """
    Endpoint pour la mise à jour du profil utilisateur.
    Seuls les champs autorisés peuvent être modifiés :
    - first_name, last_name, speciality, phone_number, address, profile_image
    
    Les champs suivants ne sont PAS modifiables (best practice) :
    - email, date_joined, membership_id, is_active, is_staff, etc.
    
    Utilise PATCH pour les mises à jour partielles.
    """
    serializer_class = UserProfileUpdateSerializer
    permission_classes = [IsAuthenticated]
    http_method_names = ['patch', 'options', 'head']

    def get_object(self):
        return self.request.user

    def update(self, request, *args, **kwargs):
        # Reject any attempt to modify read-only fields via request data
        read_only_fields = {'email', 'date_joined', 'id', 'membership_id',
                            'is_active', 'is_staff', 'is_superuser',
                            'is_email_verified', 'is_validated_by_staff',
                            'password', 'qr_code', 'document',
                            'membership_period'}
        
        attempted_readonly = set(request.data.keys()) & read_only_fields
        if attempted_readonly:
            return Response(
                {
                    "detail": f"Les champs suivants ne peuvent pas être modifiés : {', '.join(attempted_readonly)}"
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        kwargs['partial'] = True  # Always partial update
        response = super().update(request, *args, **kwargs)
        
        return Response(
            {
                "detail": "Profil mis à jour avec succès.",
                "user": response.data,
            },
            status=status.HTTP_200_OK,
        )


class PasswordChangeView(APIView):
    """
    Permet à un utilisateur connecté de changer son mot de passe.
    Exige le mot de passe actuel pour confirmer l'identité.

    POST /api/user/dashboard/change-password/
    Body: { "current_password": "...", "new_password": "...", "re_new_password": "..." }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = PasswordChangeSerializer(
            data=request.data,
            context={'request': request},
        )
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(
            {"detail": "Mot de passe modifié avec succès."},
            status=status.HTTP_200_OK,
        )


class PasswordResetRequestView(APIView):
    """
    Request a password reset link.
    """
    permission_classes = [AllowAny]

    def post(self, request):
        email = request.data.get("email")
        if not email:
            return Response({"detail": "L'email est requis."}, status=status.HTTP_400_BAD_REQUEST)
            
        try:
            user = User.objects.get(email=email)
            send_password_reset_email(user)
        except User.DoesNotExist:
            pass # Silent failure for security

        return Response({"detail": "Un lien de réinitialisation a été envoyé si l'email existe."}, status=status.HTTP_200_OK)


class PasswordResetConfirmView(APIView):
    """
    Confirm password reset.
    """
    permission_classes = [AllowAny] 
    
    def post(self, request):
        uid = request.data.get("uid")
        token = request.data.get("token")
        new_password = request.data.get("new_password")

        if not all([uid, token, new_password]):
            return Response({"detail": "UID, token et nouveau mot de passe requis."}, status=status.HTTP_400_BAD_REQUEST)

        try:
            user_id = force_str(urlsafe_base64_decode(uid))
            user = User.objects.get(pk=user_id)
        except (User.DoesNotExist, ValueError, TypeError):
            return Response({"detail": "Lien invalide."}, status=status.HTTP_400_BAD_REQUEST)

        if not default_token_generator.check_token(user, token):
            return Response({"detail": "Token invalide ou expiré."}, status=status.HTTP_400_BAD_REQUEST)

        user.set_password(new_password)
        user.save()

        return Response({"detail": "Mot de passe réinitialisé avec succès."}, status=status.HTTP_200_OK)