import pytest, uuid, os
from django.contrib.auth import get_user_model
from model_bakery import baker
from unittest.mock import patch
from users.models import UserInvitation
from datetime import timedelta

Users = get_user_model()
IS_TEMP_EMAIL_SERVICE = bool(os.environ.get("IS_TEMP_EMAIL_SERVICE", False))


@pytest.mark.django_db()
class TestUsersModel:
    def test_return_str_model_for_admin(self):
        user = baker.make(Users, is_superuser=True, role="Superuser")
        assert str(user) == f"{user.email} - Administrator - {user.role}"

    def test_return_str_model_for_non_admin(self):
        user = baker.make(Users, is_staff=False, is_superuser=False)
        assert str(user) == f"{user.email} - User - {user.role}"


@pytest.mark.django_db()
class TestUserInvitationModel:
    def test_return_str_model_for_admin(self):
        user = baker.make(Users, is_superuser=False, is_staff=False)
        user_invitation = baker.make(
            UserInvitation, invitee=user, invitor_email="admin@gmail.com"
        )
        assert (
            str(user_invitation)
            == f"{user_invitation.invitee.email} - Invited by {user_invitation.invitor_email}"
        )


# Create your tests here.
@pytest.mark.django_db()
class TestUsersLogin:
    def setup_method(self):
        self.user = baker.make(
            Users,
            email="test@example.com",
            password="password123",
            is_staff=False,
            is_superuser=False,
        )

    def test_login__with_non_existing_account_return_404(self, api_client):
        data = {"email": "test_1@example.com", "password": "password123"}

        response = api_client.post("/api/users/login/", data)

        assert response.status_code == 404
        assert (
            response.data["message"]
            == f"User {data['email']} does not exists, or not yet active."
        )

    def test_login__with_wrong_password_return_400(self, api_client):
        data = {"email": "test@example.com", "password": "password12345"}

        response = api_client.post("/api/users/login/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Invalid password. Try again."

    def test_login_return_200(self, api_client):
        data = {"email": "test@example.com", "password": "password123"}

        response = api_client.post("/api/users/login/", data)

        assert response.status_code == 200
        assert response.data["message"] == "Login successful."


@pytest.mark.django_db()
class TestUsersLogout:
    def setup_method(self):
        self.user = baker.make(
            Users,
            email="test@example.com",
            password="password123",
            is_staff=False,
            is_superuser=False,
        )

    def test_logout_but_not_authenticated_return_401(self, api_client):
        response = api_client.post("/api/users/logout/")

        assert response.status_code == 401

    def test_logout_return_200(self, authenticate_user):
        api_client = authenticate_user(user=self.user)

        response = api_client.post("/api/users/logout/")

        assert response.status_code == 200
        assert (
            response.data["message"]
            == f"User '{self.user.email}' logged out successfully."
        )


@pytest.mark.django_db()
class TestUsersMeView:
    def setup_method(self):
        self.user = baker.make(
            Users,
            email="test@example.com",
            password="password123",
            is_staff=False,
            is_superuser=False,
        )

    def test_me_api_but_not_authenticated_return_401(self, api_client):
        response = api_client.get("/api/users/me/")

        assert response.status_code == 401

    def test_me_api_return_200(self, authenticate_user):
        api_client = authenticate_user(user=self.user)

        response = api_client.get("/api/users/me/")

        assert response.status_code == 200
        assert "id" in response.data
        assert response.data["id"] == str(self.user.id)


@pytest.mark.django_db()
class TestUsersRegistration:
    def setup_method(self):
        self.user = baker.make(
            Users, email="test@example.com", is_staff=False, is_superuser=False
        )

    def test_register_with_existing_email_return_400(self, api_client):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": self.user.email,
            "password": "password123",
            "confirm_password": "password123",
            "terms_agreement": True,
        }

        response = api_client.post("/api/users/register/", data)

        assert response.status_code == 400
        assert response.data["message"] == f"User {data['email']} already exists."

    def test_register_without_agreeing_to_terms_agreement_return_403(self, api_client):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "test2@example.com",
            "password": "password123",
            "confirm_password": "password123",
            "terms_agreement": False,
        }

        response = api_client.post("/api/users/register/", data)

        assert response.status_code == 403
        assert (
            response.data["message"]
            == "You must agree to the terms and conditions before registering."
        )

    def test_register_without_special_character_in_password_return_400(
        self, api_client
    ):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "test2@example.com",
            "password": "Password123",
            "confirm_password": "Password123",
            "terms_agreement": True,
        }

        response = api_client.post("/api/users/register/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Error occured during registration."
        assert "detailed_error" in response.data
        assert (
            response.data["detailed_error"]
            == "New password must contain at least one special character."
        )

    def test_register_without_digit_in_password_return_400(self, api_client):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "test2@example.com",
            "password": "Passworddd_!",
            "confirm_password": "Passworddd_!",
            "terms_agreement": True,
        }

        response = api_client.post("/api/users/register/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Error occured during registration."
        assert "detailed_error" in response.data
        assert (
            response.data["detailed_error"]
            == "New password must contain at least one digit."
        )

    def test_register_with_length_less_than_requirement_in_password_return_400(
        self, api_client
    ):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "test2@example.com",
            "password": "pass_",
            "confirm_password": "pass_",
            "terms_agreement": True,
        }

        response = api_client.post("/api/users/register/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Error occured during registration."
        assert "detailed_error" in response.data
        assert (
            response.data["detailed_error"]
            == "New Password must be at least 8 characters long."
        )

    def test_register_with_password_and_confirm_password_not_similar_return_400(
        self, api_client
    ):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "test2@example.com",
            "password": "Password123_",
            "confirm_password": "Password1234_",
            "terms_agreement": True,
        }

        response = api_client.post("/api/users/register/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Error occured during registration."
        assert "detailed_error" in response.data
        assert response.data["detailed_error"] == "Passwords do not match."

    @patch("users.views.send_mail")
    @patch("users.views.user_registration_notification_email")
    def test_register_return_201(
        self, mock_temp_send_email, mock_send_email, api_client
    ):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "test2@example.com",
            "password": "Password123_",
            "confirm_password": "Password123_",
            "terms_agreement": True,
        }

        response = api_client.post("/api/users/register/", data)

        assert response.status_code == 201
        assert (
            response.data["message"]
            == "Account created. Please wait for the approval to be one of our designers."
        )

        if IS_TEMP_EMAIL_SERVICE:
            mock_temp_send_email.assert_called_once()
        else:
            mock_send_email.assert_called_once()


@pytest.mark.django_db()
class TestUsersForgotPassword:
    def setup_method(self):
        self.user = baker.make(
            Users, email="test@example.com", is_staff=False, is_superuser=False
        )

    def test_forgot_password_with_non_existing_email_return_404(self, api_client):
        data = {"email": "test1@example.com"}

        response = api_client.post("/api/users/forgot-password/", data)

        assert response.status_code == 404

    def test_forgot_password_with_non_active_email_return_404(self, api_client):
        data = {"email": "test1@example.com"}

        response = api_client.post("/api/users/forgot-password/", data)

        assert response.status_code == 404

    @patch("users.views.send_mail")
    @patch("users.views.reset_password_email")
    def test_forgot_password_return_200(
        self, mock_temp_send_email, mock_send_email, api_client
    ):
        data = {"email": "test@example.com"}

        response = api_client.post("/api/users/forgot-password/", data)

        assert response.status_code == 200
        assert response.data["message"] == "Reset password link sent to your email."

        if IS_TEMP_EMAIL_SERVICE:
            mock_temp_send_email.assert_called_once()
        else:
            mock_send_email.assert_called_once()


@pytest.mark.django_db()
class TestUsersResetPassword:
    def setup_method(self):
        self.user = baker.make(
            Users,
            email="test@example.com",
            reset_password_token="TOKEN1",
            password="Password123_",
            is_staff=False,
            is_superuser=False,
        )

    def test_reset_password_with_no_token_input_return_400(self, api_client):
        data = {
            "new_password": "NewPassword123_",
            "confirm_password": "NewPassword123_",
        }

        response = api_client.post("/api/users/reset-password/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Token is required to reset your password."

    def test_reset_password_with_no_existing_token_return_400(self, api_client):
        data = {
            "token": "TOKEN2",
            "new_password": "NewPassword123_",
            "confirm_password": "NewPassword123_",
        }

        response = api_client.post("/api/users/reset-password/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == "Token not found. Please request a new reset password link."
        )

    def test_forgot_password_with_existing_token_return_200(self, api_client):
        data = {
            "token": "TOKEN1",
            "new_password": "NewPassword123_",
            "confirm_password": "NewPassword123_",
        }

        response = api_client.post("/api/users/reset-password/", data)

        assert response.status_code == 200
        assert response.data["message"] == "Password successfully changed"

    def test_forgot_password_with_new_password_same_with_old_password_return_200(
        self, api_client
    ):
        data = {
            "token": "TOKEN1",
            "new_password": "Password123_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/reset-password/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == "Error occurred: New password cannot be the same as the old password."
        )


@pytest.mark.django_db()
class TestUsersChangePassword:
    def setup_method(self):
        self.user = baker.make(
            Users,
            email="test@example.com",
            reset_password_token="TOKEN1",
            password="Password123_",
            is_staff=False,
            is_superuser=False,
        )

    def test_change_password_without_old_password_input_return_400(
        self, authenticate_user
    ):
        api_client = authenticate_user(user=self.user)

        data = {
            "new_password": "NewPassword123_",
            "confirm_password": "NewPassword123_",
        }

        response = api_client.post("/api/users/change-password/", data)

        assert response.status_code == 400

    def test_change_password_with_invalid_old_password_return_400(
        self, authenticate_user
    ):
        api_client = authenticate_user(user=self.user)

        data = {
            "old_password": "Password1234_",
            "new_password": "NewPassword123_",
            "confirm_password": "NewPassword123_",
        }

        response = api_client.post("/api/users/change-password/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == "Error occurred: Invalid old password. Entered old password is not the same with the current password. Try again."
        )

    def test_change_password_return_200(self, authenticate_user):
        api_client = authenticate_user(user=self.user)

        data = {
            "old_password": "Password123_",
            "new_password": "NewPassword123_",
            "confirm_password": "NewPassword123_",
        }

        response = api_client.post("/api/users/change-password/", data)

        assert response.status_code == 200
        assert response.data["message"] == "Password successfully changed"


@pytest.mark.django_db()
class TestUsersFetch:
    def setup_method(self):
        self.admin = baker.make(Users, is_staff=True)
        self.user = baker.make(Users, is_staff=False, is_superuser=False)

        for i in range(0, 3):
            baker.make(
                Users, email=f"User_{i}@test.com", is_staff=True, is_superuser=False
            )

    def test_fetch_users_by_non_authenticated_user_return_empty_and_200(
        self, api_client
    ):
        response = api_client.get("/api/users/user-view/")

        assert response.status_code == 200
        assert response.data["results"] == []

    def test_fetch_users_by_non_admin_return_self_details_and_200(
        self, authenticate_user
    ):
        api_client = authenticate_user(user=self.user)

        response = api_client.get("/api/users/user-view/")

        assert response.status_code == 200
        assert "id" in response.data["results"][0]
        assert response.data["results"][0]["id"] == str(self.user.id)

    def test_fetch_users_by_admin_return_all_and_200(self, authenticate_user):
        api_client = authenticate_user(user=self.admin)

        response = api_client.get("/api/users/user-view/")

        assert response.status_code == 200
        assert response.data["count"] == 4


@pytest.mark.django_db()
class TestUsersCreate:
    def setup_method(self):
        self.admin = baker.make(Users, is_staff=True)
        self.user = baker.make(
            Users, email="user@example.com", is_staff=False, is_superuser=False
        )

    def test_create_user_by_non_admin_return_403(self, authenticate_user):
        api_client = authenticate_user(user=self.user)

        data = {
            "first_name": "john",
            "last_name": "doe",
            "email": "testuser@example.com",
        }

        response = api_client.post("/api/users/user-view/", data)

        assert response.status_code == 403

    def test_create_user_admin_with_existing_email_return_403(self, authenticate_user):
        api_client = authenticate_user(user=self.admin)

        data = {
            "first_name": "john",
            "last_name": "doe",
            "email": "user@example.com",
        }

        response = api_client.post("/api/users/user-view/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == f"A user with email '{data['email']}' already exists."
        )

    def test_create_user_by_admin_return_201(self, authenticate_user):
        api_client = authenticate_user(user=self.admin)

        data = {
            "first_name": "john",
            "last_name": "doe",
            "email": "testuser@example.com",
        }

        response = api_client.post("/api/users/user-view/", data)

        assert response.status_code == 201
        assert "id" in response.data
        assert response.data["email"] == data["email"]


@pytest.mark.django_db()
class TestUsersApproval:
    def setup_method(self):
        self.admin = baker.make(Users, is_staff=True)
        self.user = baker.make(
            Users,
            email="user@example.com",
            is_active=False,
            is_approved=False,
            is_staff=False,
            is_superuser=False,
        )

    def test_approve_users_by_non_admin_return_403(self, authenticate_user):
        api_client = authenticate_user()

        data = {"id": str(self.user.id), "status": "approve"}

        response = api_client.post("/api/users/approval/", data)

        assert response.status_code == 403

    def test_approve_users_by_admin_with_invalid_status_input_return_400(
        self, authenticate_user
    ):
        api_client = authenticate_user(user=self.admin)

        data = {"id": str(self.user.id), "status": "accept"}

        response = api_client.post("/api/users/approval/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Invalid status. 'approve' or 'reject' only."

    def test_approve_users_by_admin_with_non_existing_user_id_return_404(
        self, authenticate_user
    ):
        api_client = authenticate_user(user=self.admin)

        random_uuid = str(uuid.uuid4())
        data = {"id": random_uuid, "status": "accept"}

        response = api_client.post("/api/users/approval/", data)

        assert response.status_code == 404
        assert response.data["message"] == "User not found."

    @patch("users.views.send_mail")
    @patch("users.views.designer_invitation_email")
    def test_approve_users_by_admin_return_200(
        self, mock_temp_send_mail, mock_send_mail, authenticate_user
    ):
        api_client = authenticate_user(user=self.admin)

        data = {"id": str(self.user.id), "status": "approve"}

        response = api_client.post("/api/users/approval/", data)

        assert response.status_code == 200
        assert response.data["is_approved"] == True
        assert response.data["message"] == f"{self.user.email} was approved."

        if IS_TEMP_EMAIL_SERVICE:
            mock_temp_send_mail.assert_called_once()
        else:
            mock_send_mail.assert_called_once()

    def test_reject_users_by_admin_return_200(self, authenticate_user):
        api_client = authenticate_user(user=self.admin)

        data = {"id": str(self.user.id), "status": "reject"}

        response = api_client.post("/api/users/approval/", data)

        assert response.status_code == 200
        assert response.data["is_approved"] == False
        assert response.data["message"] == f"{self.user.email} was rejected."


@pytest.mark.django_db()
class TestUsersInvitation:
    def setup_method(self):
        self.admin = baker.make(Users, is_staff=True)
        self.user = baker.make(
            Users,
            email="user@example.com",
            is_active=True,
            is_staff=True,
            is_superuser=False,
        )

        self.invitee = baker.make(
            Users,
            email="testuser@example.com",
            is_active=False,
            is_staff=True,
        )

        self.invitee_2 = baker.make(
            Users,
            email="testuser2@example.com",
            is_active=False,
            is_staff=True,
        )

        self.invitations = baker.make(UserInvitation, invitee=self.invitee_2)

    def test_fetch_invitations_by_non_admin_return_403(self, authenticate_user):
        api_client = authenticate_user()

        response = api_client.get("/api/users/user-invitation/")

        assert response.status_code == 403

    def test_fetch_invitations_by_admin_return_200(self, authenticate_user):
        api_client = authenticate_user(user=self.admin)

        response = api_client.get("/api/users/user-invitation/")

        print(response.data)
        assert response.status_code == 200
        assert response.data["count"] == 1

    def test_invite_user_with_active_email_return_400(self, authenticate_user):
        api_client = authenticate_user(user=self.admin)

        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "user@example.com",
            "role": "Copywriter",
        }

        response = api_client.post("/api/users/user-invitation/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == f"Account with email '{data['email']}' is already an active user."
        )

    def test_invite_user_with_unexpired_invitation_return_400(self, authenticate_user):
        api_client = authenticate_user(user=self.admin)

        baker.make(
            UserInvitation,
            invitee=self.invitee,
            invitor_email=self.admin.email,
            expires_at=self.invitee.created_at + timedelta(hours=1),
        )

        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "testuser@example.com",
            "role": "Copywriter",
        }

        response = api_client.post("/api/users/user-invitation/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == f"An unexpired invitation for {data['email']} has already been sent."
        )

    @patch("users.views.send_mail")
    @patch("users.views.staff_invitation_email")
    def test_invite_user_with_expired_invitation_return_200(
        self, mock_temp_send_mail, mock_send_mail, authenticate_user
    ):
        api_client = authenticate_user(user=self.admin)

        baker.make(
            UserInvitation,
            invitee=self.invitee,
            invitor_email=self.admin.email,
            expires_at=self.invitee.created_at - timedelta(hours=1),
        )

        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "testuser@example.com",
            "role": "Copywriter",
        }

        response = api_client.post("/api/users/user-invitation/", data)

        assert response.status_code == 200
        assert response.data["message"] == f"Invitation sent to {data['email']}."

        if IS_TEMP_EMAIL_SERVICE:
            mock_temp_send_mail.assert_called_once()
        else:
            mock_send_mail.assert_called_once()

    @patch("users.views.send_mail")
    @patch("users.views.staff_invitation_email")
    def test_invite_non_active_user_with_no_invitation_return_200(
        self, mock_temp_send_mail, mock_send_mail, authenticate_user
    ):
        api_client = authenticate_user(user=self.admin)

        data = {
            "first_name": "John",
            "last_name": "Doe",
            "email": "testuser@example.com",
            "role": "Copywriter",
        }

        response = api_client.post("/api/users/user-invitation/", data)

        assert response.status_code == 200
        assert response.data["message"] == f"Invitation sent to {data['email']}."

        if IS_TEMP_EMAIL_SERVICE:
            mock_temp_send_mail.assert_called_once()
        else:
            mock_send_mail.assert_called_once()

    def test_invitee_register_without_invitation_return_404(self, api_client):
        data = {
            "email": "testuser@example.com",
            "password": "Password123_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/user-invitation/register/", data)

        assert response.status_code == 404
        assert (
            response.data["message"]
            == f"No active invitation found for email '{data['email']}'."
        )

    def test_invitee_register_with_non_existing_email_return_404(self, api_client):
        self.invitee.delete()

        data = {
            "email": "testuser@example.com",
            "password": "Password123_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/user-invitation/register/", data)

        assert response.status_code == 404
        assert (
            response.data["message"]
            == f"User with email '{data['email']}' does not exist."
        )

    def test_invitee_register_with_active_email_return_400(self, api_client):
        self.invitee.is_active = True
        self.invitee.save()

        baker.make(
            UserInvitation,
            invitee=self.invitee,
            invitor_email=self.admin.email,
            expires_at=self.invitee.created_at + timedelta(hours=1),
        )

        data = {
            "email": "testuser@example.com",
            "password": "Password123_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/user-invitation/register/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == f"Account with email '{data['email']}' is already active."
        )

    def test_invitee_register_that_is_not_staff_return_400(self, api_client):
        self.invitee.is_staff = False
        self.invitee.save()

        baker.make(
            UserInvitation,
            invitee=self.invitee,
            invitor_email=self.admin.email,
            expires_at=self.invitee.created_at + timedelta(hours=1),
        )

        data = {
            "email": "testuser@example.com",
            "password": "Password123_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/user-invitation/register/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == f"Account with email '{data['email']}' is not an invited staff account."
        )

    def test_invitee_register_with_expired_invitation_return_400(self, api_client):
        baker.make(
            UserInvitation,
            invitee=self.invitee,
            invitor_email=self.admin.email,
            expires_at=self.invitee.created_at - timedelta(hours=1),
        )

        data = {
            "email": "testuser@example.com",
            "password": "Password123_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/user-invitation/register/", data)

        assert response.status_code == 400
        assert (
            response.data["message"]
            == "The invitation is already expired. Please request for another invitation."
        )

    def test_invitee_register_with_wrong_password_return_400(self, api_client):
        baker.make(
            UserInvitation,
            invitee=self.invitee,
            invitor_email=self.admin.email,
            expires_at=self.invitee.created_at + timedelta(hours=1),
        )

        data = {
            "email": "testuser@example.com",
            "password": "Password1234_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/user-invitation/register/", data)

        assert response.status_code == 400
        assert response.data["message"] == "Passwords do not match."

    def test_invitee_register_with_correct_input_return_201(self, api_client):
        baker.make(
            UserInvitation,
            invitee=self.invitee,
            invitor_email=self.admin.email,
            expires_at=self.invitee.created_at + timedelta(hours=1),
        )

        data = {
            "email": "testuser@example.com",
            "password": "Password123_",
            "confirm_password": "Password123_",
        }

        response = api_client.post("/api/users/user-invitation/register/", data)

        assert response.status_code == 201
        assert (
            response.data["message"]
            == f"User '{data['email']}' registered successfully."
        )
