import uuid

import pytest

from r2r import R2RClient, R2RException

# @pytest.fixture  # (scope="session")
# def client(config):
#     """A client logged in as a superuser."""
#     client = R2RClient(config.base_url)
#     client.users.login(config.superuser_email, config.superuser_password)
#     yield client


@pytest.fixture
def normal_user_client(mutable_client):
    """Create a normal user and log in with that user."""
    # client = R2RClient(config.base_url)

    email = f"normal_{uuid.uuid4()}@test.com"
    password = "normal_password"
    user_resp = mutable_client.users.create(email, password)
    mutable_client.users.login(email, password)

    yield mutable_client

    # Cleanup: Try deleting the normal user if exists
    try:
        mutable_client.users.login(email, password)
        mutable_client.users.delete(
            mutable_client.users.me()["results"]["id"], password
        )
    except R2RException:
        pass


@pytest.fixture
def another_normal_user_client(config):
    """Create another normal user and log in with that user."""
    client = R2RClient(config.base_url)

    email = f"another_{uuid.uuid4()}@test.com"
    password = "another_password"
    user_resp = client.users.create(email, password)
    client.users.login(email, password)
    yield client

    # Cleanup: Try deleting the user if exists
    try:
        client.users.login(email, password)
        client.users.delete(client.users.me()["results"]["id"], password)
    except R2RException:
        pass


@pytest.fixture
def user_owned_collection(normal_user_client):
    """Create a collection owned by the normal user."""
    resp = normal_user_client.collections.create(
        name="User Owned Collection",
        description="A collection owned by a normal user",
    )
    coll_id = resp["results"]["id"]
    yield coll_id
    # Cleanup
    try:
        normal_user_client.collections.delete(coll_id)
    except R2RException:
        pass


@pytest.fixture
def superuser_owned_collection(client):
    """Create a collection owned by the superuser."""
    resp = client.collections.create(
        name="Superuser Owned Collection",
        description="A collection owned by superuser",
    )
    coll_id = resp["results"]["id"]
    yield coll_id
    # Cleanup
    try:
        client.collections.delete(coll_id)
    except R2RException:
        pass


def test_non_member_cannot_view_collection(
    normal_user_client, superuser_owned_collection
):
    """A normal user (not a member of a superuser-owned collection) tries to view it."""
    # The normal user is not added to the superuser collection, should fail
    with pytest.raises(R2RException) as exc_info:
        normal_user_client.collections.retrieve(superuser_owned_collection)
    assert (
        exc_info.value.status_code == 403
    ), "Non-member should not be able to view collection."


def test_collection_owner_can_view_collection(
    normal_user_client, user_owned_collection
):
    """The owner should be able to view their own collection."""
    coll = normal_user_client.collections.retrieve(user_owned_collection)[
        "results"
    ]
    assert (
        coll["id"] == user_owned_collection
    ), "Owner cannot view their own collection."


def test_collection_member_can_view_collection(
    client, normal_user_client, user_owned_collection
):
    """A user added to a collection should be able to view it."""
    # Create another user and add them to the user's collection
    new_user_email = f"temp_member_{uuid.uuid4()}@test.com"
    new_user_password = "temp_member_password"

    # Store normal user's email before any logouts
    normal_user_email = normal_user_client.users.me()["results"]["email"]

    # Create a new user and log in as them
    member_client = R2RClient(normal_user_client.base_url)
    member_client.users.create(new_user_email, new_user_password)
    member_client.users.login(new_user_email, new_user_password)
    member_id = member_client.users.me()["results"]["id"]

    # Owner adds the new user to the collection
    normal_user_client.users.logout()
    normal_user_client.users.login(normal_user_email, "normal_password")

    normal_user_client.collections.add_user(user_owned_collection, member_id)

    # The member now can view the collection
    coll = member_client.collections.retrieve(user_owned_collection)["results"]
    assert coll["id"] == user_owned_collection


def test_non_owner_member_cannot_edit_collection(
    user_owned_collection, another_normal_user_client, normal_user_client
):
    """A member who is not the owner should not be able to edit the collection."""
    # Add another normal user to the owner's collection
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # Another normal user tries to update collection
    with pytest.raises(R2RException) as exc_info:
        another_normal_user_client.collections.update(
            user_owned_collection, name="Malicious Update"
        )
    assert (
        exc_info.value.status_code == 403
    ), "Non-owner member should not be able to edit."


def test_non_owner_member_cannot_delete_collection(
    user_owned_collection, another_normal_user_client, normal_user_client
):
    """A member who is not the owner should not be able to delete the collection."""
    # Add the other user
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # Another user tries to delete
    with pytest.raises(R2RException) as exc_info:
        another_normal_user_client.collections.delete(user_owned_collection)
    assert (
        exc_info.value.status_code == 403
    ), "Non-owner member should not be able to delete."


def test_non_owner_member_cannot_add_other_users(
    user_owned_collection, another_normal_user_client, normal_user_client
):
    """A member who is not the owner should not be able to add other users."""
    # Another user tries to add a third user
    third_email = f"third_user_{uuid.uuid4()}@test.com"
    third_password = "third_password"
    # Need to create third user as a superuser or owner
    normal_user_email = normal_user_client.users.me()["results"]["email"]
    normal_user_client.users.logout()

    # Login as normal user again
    # NOTE: We assume normal_password known here; in a real scenario, store it or use fixtures more dynamically
    # This code snippet assumes we have these credentials available.
    # If not, manage credentials store in fixture creation.
    normal_user_client.users.login(normal_user_email, "normal_password")
    third_user_resp = normal_user_client.users.create(
        third_email, third_password
    )
    third_user_id = third_user_resp["results"]["id"]

    # Add another user as a member
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # Now, another_normal_user_client tries to add the third user
    with pytest.raises(R2RException) as exc_info:
        another_normal_user_client.collections.add_user(
            user_owned_collection, third_user_id
        )
    assert (
        exc_info.value.status_code == 403
    ), "Non-owner member should not be able to add users."


def test_owner_can_remove_member_from_collection(
    user_owned_collection, another_normal_user_client, normal_user_client
):
    """The owner should be able to remove a member from their collection."""
    # Add another user to the collection
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # Remove them
    remove_resp = normal_user_client.collections.remove_user(
        user_owned_collection, another_user_id
    )["results"]
    assert remove_resp["success"], "Owner could not remove member."

    # The removed user should no longer have access
    with pytest.raises(R2RException) as exc_info:
        another_normal_user_client.collections.retrieve(user_owned_collection)
    assert (
        exc_info.value.status_code == 403
    ), "Removed user still has access after removal."


def test_superuser_can_access_any_collection(client, user_owned_collection):
    """A superuser should be able to view and edit any collection."""
    # Superuser can view
    coll = client.collections.retrieve(user_owned_collection)["results"]
    assert (
        coll["id"] == user_owned_collection
    ), "Superuser cannot view a user collection."

    # Superuser can also update
    updated = client.collections.update(
        user_owned_collection, name="Superuser Edit"
    )["results"]
    assert (
        updated["name"] == "Superuser Edit"
    ), "Superuser cannot edit collection."


def test_unauthenticated_cannot_access_collections(
    config, user_owned_collection
):
    """An unauthenticated (no login) client should not access protected endpoints."""
    unauth_client = R2RClient(config.base_url)
    # we must CREATE + LOGIN as superuser is default user for unauth in basic config
    user_name = f"unauth_user_{uuid.uuid4()}@email.com"
    unauth_client.users.create(user_name, "unauth_password")
    unauth_client.users.login(user_name, "unauth_password")
    with pytest.raises(R2RException) as exc_info:
        unauth_client.collections.retrieve(user_owned_collection)
    assert (
        exc_info.value.status_code == 403
    ), "Unaurthorized user should get 403"


def test_user_cannot_add_document_to_collection_they_cannot_edit(
    client, normal_user_client
):
    """A normal user who is just a member (not owner) of a collection should not be able to add documents."""
    # Create a collection as normal user (owner)
    resp = normal_user_client.collections.create(
        name="Owned by user", description="desc"
    )
    coll_id = resp["results"]["id"]

    # Create a second user and add them as member
    second_email = f"second_{uuid.uuid4()}@test.com"
    second_password = "pwd"
    client.users.logout()
    second_client = R2RClient(normal_user_client.base_url)
    second_client.users.create(second_email, second_password)
    second_client.users.login(second_email, second_password)
    second_id = second_client.users.me()["results"]["id"]

    # Owner adds second user as a member
    email_of_normal_user = normal_user_client.users.me()["results"]["email"]
    normal_user_client.users.logout()
    # Re-login owner (assuming we stored the original user's creds)
    # For demonstration, we assume we know the normal_user_client creds or re-use fixtures carefully.
    # In a real test environment, you'd maintain credentials more robustly.
    # Here we rely on the normal_user_client fixture being re-instantiated per test if needed.
    normal_user_client.users.login(email_of_normal_user, "normal_password")
    normal_user_client.collections.add_user(coll_id, second_id)

    # Create a document as owner
    doc_resp = normal_user_client.documents.create(raw_text="Test Document")
    doc_id = doc_resp["results"]["document_id"]

    # Now second user tries to add another document (which they do not have edit rights for)
    second_client.users.logout()
    second_client.users.login(second_email, second_password)
    # Another doc created by second user (just for attempt)
    doc2_resp = second_client.documents.create(raw_text="Doc by second user")
    doc2_id = doc2_resp["results"]["document_id"]

    # Second user tries to add their doc2_id to the owner’s collection
    with pytest.raises(R2RException) as exc_info:
        second_client.collections.add_document(coll_id, doc2_id)
    assert (
        exc_info.value.status_code == 403
    ), "Non-owner member should not add documents."

    # Cleanup
    normal_user_client.collections.delete(coll_id)


def test_user_cannot_remove_document_from_collection_they_cannot_edit(
    normal_user_client,
):
    """A user who is just a member should not remove documents."""
    # Create a collection
    resp = normal_user_client.collections.create(
        name="Removable", description="desc"
    )
    coll_id = resp["results"]["id"]

    # Create a document in it
    doc_resp = normal_user_client.documents.create(raw_text="Doc in coll")
    doc_id = doc_resp["results"]["document_id"]
    normal_user_client.collections.add_document(coll_id, doc_id)

    # Create another user and add as member
    another_email = f"amember_{uuid.uuid4()}@test.com"
    another_password = "memberpwd"
    member_client = R2RClient(normal_user_client.base_url)
    member_client.users.create(another_email, another_password)
    member_client.users.login(another_email, another_password)
    member_id = member_client.users.me()["results"]["id"]
    user_email = normal_user_client.users.me()["results"]["email"]

    # Add member to collection
    normal_user_client.users.logout()
    normal_user_client.users.login(user_email, "normal_password")
    normal_user_client.collections.add_user(coll_id, member_id)

    # Member tries to remove the document
    with pytest.raises(R2RException) as exc_info:
        member_client.collections.remove_document(coll_id, doc_id)
    assert (
        exc_info.value.status_code == 403
    ), "Member should not remove documents."

    # Cleanup
    normal_user_client.collections.delete(coll_id)


def test_normal_user_cannot_make_another_user_superuser(normal_user_client):
    """A normal user tries to update another user to superuser, should fail."""
    # Create another user
    email = f"regular_{uuid.uuid4()}@test.com"
    password = "not_superuser"
    new_user_resp = normal_user_client.users.create(email, password)
    new_user_id = new_user_resp["results"]["id"]

    # Try updating their superuser status
    with pytest.raises(R2RException) as exc_info:
        normal_user_client.users.update(new_user_id, is_superuser=True)
    assert (
        exc_info.value.status_code == 403
    ), "Non-superuser should not grant superuser status."


def test_normal_user_cannot_view_other_users_if_not_superuser(
    normal_user_client,
):
    """A normal user tries to list all users, should fail."""
    with pytest.raises(R2RException) as exc_info:
        normal_user_client.users.list()
    assert (
        exc_info.value.status_code == 403
    ), "Non-superuser should not list all users."


def test_normal_user_cannot_update_other_users_details(
    normal_user_client, client
):
    """A normal user tries to update another normal user's details."""
    # Create another normal user
    email = f"other_normal_{uuid.uuid4()}@test.com"
    password = "pwd123"
    client.users.logout()
    another_client = R2RClient(normal_user_client.base_url)
    another_client.users.create(email, password)
    another_client.users.login(email, password)
    another_user_id = another_client.users.me()["results"]["id"]
    another_client.users.logout()

    # Try to update as first normal user (not superuser, not same user)
    with pytest.raises(R2RException) as exc_info:
        normal_user_client.users.update(another_user_id, name="Hacked Name")
    assert (
        exc_info.value.status_code == 403
    ), "Non-superuser should not update another user's info."


# Additional Tests for Strengthened Coverage


def test_owner_cannot_promote_member_to_superuser_via_collection(
    user_owned_collection, normal_user_client, another_normal_user_client
):
    """
    Ensures that being a collection owner doesn't confer the right
    to promote a user to superuser.
    """
    # Add another user to the collection
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # Try to update the member's superuser status
    with pytest.raises(R2RException) as exc_info:
        normal_user_client.users.update(another_user_id, is_superuser=True)
    assert (
        exc_info.value.status_code == 403
    ), "Collection owners should not grant superuser status."


def test_member_cannot_view_other_users_info(
    user_owned_collection, normal_user_client, another_normal_user_client
):
    """
    A member (non-owner) of a collection should not be able to retrieve other users' details
    outside of their allowed scope.
    """
    # Add the other normal user as a member
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # As another_normal_user_client (a member), try to retrieve owner user details
    owner_id = normal_user_client.users.me()["results"]["id"]
    with pytest.raises(R2RException) as exc_info:
        another_normal_user_client.users.retrieve(owner_id)
    assert (
        exc_info.value.status_code == 403
    ), "Members should not be able to view other users' details."


def test_unauthenticated_user_cannot_join_collection(
    config, user_owned_collection
):
    """
    An unauthenticated user should not be able to join or view collections.
    """
    unauth_client = R2RClient(config.base_url)
    # we must CREATE + LOGIN as superuser is default user for unauth in basic config
    user_name = f"unauth_user_{uuid.uuid4()}@email.com"
    unauth_client.users.create(user_name, "unauth_password")
    unauth_client.users.login(user_name, "unauth_password")

    # No login performed here, client is unauthenticated
    with pytest.raises(R2RException) as exc_info:
        unauth_client.collections.retrieve(user_owned_collection)
    assert exc_info.value.status_code in [
        401,
        403,
    ], "Unauthenticated user should not access collections."


def test_non_owner_cannot_remove_users_they_did_not_add(
    user_owned_collection, normal_user_client, another_normal_user_client
):
    """
    A member who is not the owner cannot remove other members from the collection.
    """
    # Add another user as a member
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # Now try removing that user as another_normal_user_client
    with pytest.raises(R2RException) as exc_info:
        another_normal_user_client.collections.remove_user(
            user_owned_collection, another_user_id
        )
    assert (
        exc_info.value.status_code == 403
    ), "Non-owner member should not remove other users."


def test_owner_cannot_access_deleted_member_info_after_removal(
    user_owned_collection, normal_user_client, another_normal_user_client
):
    """
    After the owner removes a user from the collection, ensure that attempts to
    perform collection-specific actions with that user fail.
    """
    # Add another user to the collection
    another_user_id = another_normal_user_client.users.me()["results"]["id"]
    normal_user_client.collections.add_user(
        user_owned_collection, another_user_id
    )

    # Remove them
    normal_user_client.collections.remove_user(
        user_owned_collection, another_user_id
    )

    # Now, try listing collections for that removed user (as owner),
    # if there's an endpoint that filters by user, to ensure no special access remains.
    # If no such endpoint exists, this test can be adapted to try another relevant action.
    # For demonstration, we might attempt to retrieve user details as owner:
    with pytest.raises(R2RException) as exc_info:
        normal_user_client.users.retrieve(another_user_id)
    # We expect a 403 because normal_user_client is not superuser and not that user.
    assert (
        exc_info.value.status_code == 403
    ), "Owner should not access removed member's user info."


def test_member_cannot_add_document_to_non_existent_collection(
    normal_user_client,
):
    """
    A member tries to add a document to a collection that doesn't exist.
    """
    fake_coll_id = str(uuid.uuid4())
    doc_resp = normal_user_client.documents.create(raw_text="Test Doc")
    doc_id = doc_resp["results"]["document_id"]
    with pytest.raises(R2RException) as exc_info:
        normal_user_client.collections.add_document(fake_coll_id, doc_id)
    assert exc_info.value.status_code in [
        400,
        404,
    ], "Expected error when adding doc to non-existent collection."
