"""
Tests for UserGroupSubject permissions without propagation.
"""

import pytest

from topdownrbac.models import (
    RoleBinding,
    UserGroupSubject,
    UserPermission,
)
from topdownrbac.tests.conftest import (
    assert_has_view,
    assert_has_view_and_change,
    assert_no_delete,
    assert_no_view,
)


@pytest.mark.django_db
class TestUserGroupPermission:
    """Test permission assignment for UserGroupSubjects."""

    def test_permissions_granted_to_all_group_members(
        self,
        user1,
        user2,
        user3,
        role1,
        hierarchy,
        group1,
        group2,
        perm_view,
        perm_change,
    ):
        """All group members receive permissions from group RoleBinding."""
        role1.add_permission(perm_view)
        role1.add_permission(perm_change)
        RoleBinding.objects.create(name="rb1", subject=group1, role=role1, restricted_object=hierarchy["obj1"])
        for user in [user1, user2]:
            assert_has_view_and_change(user, hierarchy["obj1"])
            assert_no_delete(user, hierarchy["obj1"])
        assert_no_view(user3, hierarchy["obj1"])

    def test_permissions_granted_when_perms_added_after_binding(
        self,
        user1,
        user2,
        user3,
        role1,
        hierarchy,
        group1,
        group2,
        perm_view,
        perm_change,
    ):
        """Permissions are granted when added to role after binding exists."""
        RoleBinding.objects.create(name="rb1", subject=group1, role=role1, restricted_object=hierarchy["obj1"])
        role1.add_permission(perm_view)
        role1.add_permission(perm_change)
        for user in [user1, user2]:
            assert_has_view_and_change(user, hierarchy["obj1"])
        assert_no_view(user3, hierarchy["obj1"])

    def test_permissions_removed_when_perm_removed_from_role(self, user1, user2, role1, hierarchy, group1, perm_view):
        """Removing permission from role revokes it from all group members."""
        role1.add_permission(perm_view)
        RoleBinding.objects.create(name="rb1", subject=group1, role=role1, restricted_object=hierarchy["obj1"])
        for user in [user1, user2]:
            assert_has_view(user, hierarchy["obj1"])
        role1.remove_permission(perm_view)
        for user in [user1, user2]:
            assert_no_view(user, hierarchy["obj1"])

    def test_permissions_removed_when_rolebinding_deleted(self, user1, user2, role1, hierarchy, group1, perm_view):
        """Deleting RoleBinding revokes permissions from all group members."""
        role1.add_permission(perm_view)
        rb = RoleBinding.objects.create(name="rb1", subject=group1, role=role1, restricted_object=hierarchy["obj1"])
        for user in [user1, user2]:
            assert_has_view(user, hierarchy["obj1"])
        rb.delete()
        for user in [user1, user2]:
            assert_no_view(user, hierarchy["obj1"])

    def test_permissions_scoped_to_specific_object(self, user1, role1, hierarchy, group1, perm_view):
        """Permissions only apply to the RestrictedObject in the binding."""
        role1.add_permission(perm_view)
        RoleBinding.objects.create(name="rb1", subject=group1, role=role1, restricted_object=hierarchy["obj1"])
        assert_has_view(user1, hierarchy["obj1"])
        assert_no_view(user1, hierarchy["obj2"])


@pytest.mark.django_db
class TestUserGroupMembership:
    """Test permission changes when group membership changes."""

    def test_adding_user_grants_existing_permissions(self, user1, user3, role1, hierarchy, group1, perm_view):
        """New user added to group receives existing group permissions."""
        role1.add_permission(perm_view)
        RoleBinding.objects.create(name="rb1", subject=group1, role=role1, restricted_object=hierarchy["obj1"])
        assert_no_view(user3, hierarchy["obj1"])
        group1.add_user(user3)
        assert_has_view(user3, hierarchy["obj1"])
        assert group1.user_subjects.count() == 3

    def test_removing_user_revokes_permissions(self, user1, user2, role1, hierarchy, group1, perm_view):
        """Removing user from group revokes group permissions."""
        role1.add_permission(perm_view)
        RoleBinding.objects.create(name="rb1", subject=group1, role=role1, restricted_object=hierarchy["obj1"])
        for user in [user1, user2]:
            assert_has_view(user, hierarchy["obj1"])
        group1.remove_user(user2)
        assert_has_view(user1, hierarchy["obj1"])
        assert_no_view(user2, hierarchy["obj1"])
        assert group1.user_subjects.count() == 1


@pytest.mark.django_db
class TestBulkCreateIgnoreConflicts:
    """
    When a user is added to a group that has a binding overlapping with
    an existing direct binding, bulk_create must not raise IntegrityError.
    """

    def test_adding_user_to_group_with_overlapping_binding_no_error(
        self,
        user1,
        role1,
        obj1,
        perm_view,
    ):
        """
        User has a direct binding. Adding user to a group with the same
        role on the same object must not raise IntegrityError.
        """
        role1.add_permission(perm_view)
        RoleBinding.objects.create(
            name="rb_direct",
            subject=user1,
            role=role1,
            restricted_object=obj1,
        )

        group = UserGroupSubject.objects.create()
        RoleBinding.objects.create(
            name="rb_group",
            subject=group,
            role=role1,
            restricted_object=obj1,
        )

        group.add_user(user1)

        assert_has_view(user1, obj1)

    def test_adding_user_to_group_with_propagated_overlapping_binding(
        self,
        user1,
        role1,
        hierarchy,
        perm_view,
    ):
        """
        User has propagated binding on obj1. Adding user to group with
        same propagated binding must not error.
        """
        role1.add_permission(perm_view)
        RoleBinding.objects.create(
            name="rb_user",
            subject=user1,
            role=role1,
            restricted_object=hierarchy["obj1"],
            propagate=True,
        )

        group = UserGroupSubject.objects.create()
        RoleBinding.objects.create(
            name="rb_group",
            subject=group,
            role=role1,
            restricted_object=hierarchy["obj1"],
            propagate=True,
        )

        group.add_user(user1)

        for obj in hierarchy["all_objects"]:
            assert_has_view(user1, obj)

    def test_add_single_perm_twice_with_same_source_no_error(
        self,
        user1,
        role1,
        obj1,
        perm_view,
    ):
        """
        Calling _add_single_perm_to_subject twice for the same perm/user/object
        must not raise IntegrityError (needs ignore_conflicts=True in bulk_create).
        """
        role1.add_permission(perm_view)
        rb = RoleBinding.objects.create(name="rb", subject=user1, role=role1, restricted_object=obj1)

        count_before = UserPermission.objects.filter(
            user=user1, perm=perm_view, restricted_object=obj1, source=rb
        ).count()
        assert count_before == 1

        # This must not raise IntegrityError
        rb._add_single_perm_to_subject(perm_view)

        count_after = UserPermission.objects.filter(
            user=user1, perm=perm_view, restricted_object=obj1, source=rb
        ).count()
        assert count_after == 1
