from django.conf import settings
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ObjectDoesNotExist
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from rest_framework import status
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken

from apps.account.models import User
from apps.account.serializers import admin_account_serializers
from apps.account.services.mail_services import send_reset_password_mail


class AdminLoginView(CreateAPIView):
    """
    This is the AdminLoginView class responsible for handling user login through a POST request.

    It allows administrators to log in and generate an authentication token.

    Attributes:
        permission_classes (tuple): Permissions required to access this view (AllowAny for public access).
        http_method_names (list): The allowed HTTP methods, in this case, only POST is allowed.

    Methods:
        post(request): Handles user login and token generation logic.
    """

    permission_classes = (AllowAny,)
    http_method_names = ["post"]

    def post(self, request, *args, **kwargs):
        """
        Handles user login and token generation logic.

        Args:
            request (HttpRequest): The HTTP request containing user data.

        Returns:
            Response: The serialized user data with an authentication token or an error response.
        """

        # Deserialize the incoming data using the AdminLoginSerializer
        serializer = admin_account_serializers.AdminLoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        email = request.data.get("email")
        password = request.data.get("password")

        try:
            # Check if the user with the given email exists
            user = User.objects.get(email=email)

            # Verify the user's password
            if user.check_password(password):
                # Generate a new access token using the RefreshToken
                refresh = RefreshToken.for_user(user)

                response_data = {
                    "id": user.pk,
                    "email": user.email,
                    "name": user.name,
                    "token": str(refresh.access_token),
                    "refresh": str(refresh),
                }

                return Response(response_data)
            else:
                # Password is incorrect
                return Response(
                    {
                        "message": "You have entered the wrong password. Please try again with the correct password."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )
        except ObjectDoesNotExist:
            # User with the provided email does not exist
            return Response(
                {
                    "message": "The entered email address does not exist. Please sign up to continue."
                },
                status=status.HTTP_404_NOT_FOUND,
            )


class AdminChangePasswordView(CreateAPIView):
    """
    This is the AdminChangePasswordView class responsible for handling password change through a POST request.

    It allows administrators to change their password by providing their old and new passwords.

    Attributes:
        permission_classes (tuple): Permissions required to access this view (AllowAny for public access).
        http_method_names (list): The allowed HTTP methods, in this case, only POST is allowed.

    Methods:
        post(request): Handles password change logic.
    """

    permission_classes = (IsAuthenticated,)
    http_method_names = ["post"]

    def post(self, request, *args, **kwargs):
        """
        Handles password change logic.

        Args:
            request (HttpRequest): The HTTP request containing user data.

        Returns:
            Response: A response indicating the success or failure of the password change operation.
        """

        # Deserialize the incoming data using the AdminChangePasswordSerializer
        serializer = admin_account_serializers.AdminChangePasswordSerializer(
            data=request.data
        )
        serializer.is_valid(raise_exception=True)

        email = request.data.get("email")
        old_password = request.data.get("old_password")
        new_password = request.data.get("new_password")

        try:
            # Check if the administrator with the given email exists
            user = User.objects.get(email=email, is_superuser=True)

            # Verify the user's old password
            if user.check_password(old_password):
                # Set the new password
                user.set_password(new_password)
                user.save()
                return Response(
                    {"message": "Password has been changed successfully."},
                    status=status.HTTP_200_OK,
                )
            else:
                # The old password provided is incorrect
                return Response(
                    {
                        "message": "Your old password does not match. Please try again with the correct password."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )
        except ObjectDoesNotExist:
            # No user with the provided email exists
            return Response(
                {"message": "The entered email address does not exist."},
                status=status.HTTP_404_NOT_FOUND,
            )


class ForgotPasswordView(CreateAPIView):
    """
    This is the ForgotPasswordView class responsible for handling password reset requests.

    It allows users to request a password reset by providing their email address. A password reset link
    with a time-limited token is then sent to the user's email address.

    Attributes:
        permission_classes (tuple): Specifies the permissions required to access this view, set to AllowAny for public access.
        http_method_names (list): The allowed HTTP methods, in this case, only POST is allowed.

    Methods:
        post(request, *args, **kwargs): Handles the password reset request, including generating and sending reset links.
    """

    permission_classes = (AllowAny,)
    http_method_names = ["post"]

    def post(self, request, *args, **kwargs):
        """
        Handles the password reset request, including generating and sending reset links.

        Args:
            request (HttpRequest): The HTTP request containing the user's email address.
            *args: Variable-length argument list.
            **kwargs: Arbitrary keyword arguments.

        Returns:
            Response: A response indicating the success of the password reset request.
        """

        email = request.data.get("email")

        try:
            # Check if the administrator with the given email exists
            user = User.objects.get(email=email, is_superuser=True)
        except ObjectDoesNotExist:
            # No user with the provided email exists
            return Response(
                {"message": "The entered email address does not exist."},
                status=status.HTTP_404_NOT_FOUND,
            )

        # Generate a password reset token with an expiration time
        uid = urlsafe_base64_encode(force_bytes(user.pk))
        token = default_token_generator.make_token(user)

        # Create a password reset link with the token
        reset_url = f"{settings.FORGOT_PASSWORD_LINK}{uid}/{token}"

        send_reset_password_mail(user, reset_url)

        return Response(
            {
                "message": "You will receive an email with instructions to reset your password."
            },
            status=status.HTTP_200_OK,
        )


class ResetPasswordView(APIView):
    """
    This is the ResetPasswordView class responsible for handling password reset requests.

    It allows users to reset their passwords by providing a valid reset token along with a new password.

    Attributes:
        permission_classes (tuple): Specifies the permissions required to access this view, set to AllowAny for public access.

    Methods:
        post(request, uidb64, token): Handles the password reset logic.
    """

    permission_classes = (AllowAny,)

    def post(self, request, uidb64, token):
        """
        Handles the password reset logic.

        Args:
            request (HttpRequest): The HTTP request containing the new password.
            uidb64 (str): A base64-encoded user ID for identification.
            token (str): A token for password reset

        Returns:
            Response: A response indicating the success or failure of the password reset.
        """
        new_password = request.data.get("new_password")
        email = request.data.get("email")

        if new_password is None:
            return Response(
                {"message": "New password required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if email is None:
            return Response(
                {"message": "Email is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Decode the user ID and token
        decoded_bytes = urlsafe_base64_decode(uidb64)
        uid = decoded_bytes.decode("utf-8")
        try:
            user = User.objects.get(pk=uid, email=email)
        except ObjectDoesNotExist:
            # No user with the provided email exists
            return Response(
                {"message": "User does not exist"},
                status=status.HTTP_404_NOT_FOUND,
            )

        try:
            # Check if the token is valid and not expired
            if default_token_generator.check_token(user, token):
                user.set_password(new_password)
                user.save()
                return Response(
                    {"message": "Password has been reset successful"},
                    status=status.HTTP_200_OK,
                )
            else:
                return Response(
                    {"message": "Invalid or expired token"},
                    status=status.HTTP_400_BAD_REQUEST,
                )
        except Exception as e:
            return Response(
                {"message": "Invalid or expired token"},
                status=status.HTTP_400_BAD_REQUEST,
            )
