"""
Tests for dual inheritance: a model that inherits from both
RestrictedObject and UserGroupSubject.

These tests exercise scenarios that ONLY arise because a single
instance can simultaneously act as:
  1. A hierarchy node (RestrictedObject / MP_Node tree)
  2. A user group (UserGroupSubject with members)

The DualInheritanceModel is defined in topdownrbac.tests.testapp.models
and registered only during test runs.
"""

import pytest
from django.contrib.auth.models import Permission

from topdownrbac.models import (
    Role,
    RoleBinding,
    UserSubject,
)
from topdownrbac.tests.testapp.models import DualInheritanceModel

PERM_VIEW = "view_dualinheritancemodel"
PERM_CHANGE = "change_dualinheritancemodel"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _has_perm(user, codename, obj):
    return user.has_perm(codename, obj=obj)


# ---------------------------------------------------------------------------
# Tests: dual-inheritance node acting as both group and hierarchy node
# ---------------------------------------------------------------------------


@pytest.mark.django_db
class TestDualModelAsGroupAndNode:
    """
    A DualInheritanceModel that is BOTH a node in the hierarchy AND the
    subject (group) of a RoleBinding.  This is the core dual-inheritance
    scenario.
    """

    def test_as_group_subject_on_itself(self, user1, dual_role_viewer):
        """Instance can be both the group-subject AND the restricted_object."""
        obj = DualInheritanceModel.objects.create(name="self_ref")
        obj.add_user(user1)
        RoleBinding.objects.create(name="rb", subject=obj, role=dual_role_viewer, restricted_object=obj)
        assert _has_perm(user1, PERM_VIEW, obj)

    def test_group_binding_on_sibling_node(self, user1, dual_role_viewer):
        """A group instance can grant permissions on a sibling instance."""
        root = DualInheritanceModel.objects.create(name="root")
        group_node = DualInheritanceModel.objects.create(name="group", parent=root)
        target_node = DualInheritanceModel.objects.create(name="target", parent=root)
        group_node.add_user(user1)
        RoleBinding.objects.create(name="rb", subject=group_node, role=dual_role_viewer, restricted_object=target_node)
        assert _has_perm(user1, PERM_VIEW, target_node)
        assert not _has_perm(user1, PERM_VIEW, group_node)

    def test_group_propagated_binding_on_ancestor(self, user1, dual_role_viewer):
        """
        A group instance with a propagated binding on an ancestor
        grants permissions to all descendants, including itself.
        """
        root = DualInheritanceModel.objects.create(name="root")
        child = DualInheritanceModel.objects.create(name="child", parent=root)
        child.add_user(user1)
        RoleBinding.objects.create(
            name="rb",
            subject=child,
            role=dual_role_viewer,
            restricted_object=root,
            propagate=True,
        )
        assert _has_perm(user1, PERM_VIEW, root)
        assert _has_perm(user1, PERM_VIEW, child)

    def test_group_member_change_propagates_through_hierarchy(self, user1, user2, dual_role_viewer):
        """
        Adding/removing a user from a group instance updates
        propagated permissions across the hierarchy.
        """
        root = DualInheritanceModel.objects.create(name="root")
        child = DualInheritanceModel.objects.create(name="child", parent=root)
        group_node = DualInheritanceModel.objects.create(name="group")
        group_node.add_user(user1)

        RoleBinding.objects.create(
            name="rb",
            subject=group_node,
            role=dual_role_viewer,
            restricted_object=root,
            propagate=True,
        )
        assert _has_perm(user1, PERM_VIEW, child)
        assert not _has_perm(user2, PERM_VIEW, child)

        group_node.add_user(user2)
        assert _has_perm(user2, PERM_VIEW, child)

        group_node.remove_user(user1)
        assert not _has_perm(user1, PERM_VIEW, child)

    def test_new_child_inherits_group_propagated_perms(self, user1, user2, dual_role_viewer):
        """
        A new instance added under a subtree with a propagated
        group-binding inherits permissions for all group members.
        """
        root = DualInheritanceModel.objects.create(name="root")
        group_node = DualInheritanceModel.objects.create(name="group")
        group_node.add_user(user1)
        group_node.add_user(user2)

        RoleBinding.objects.create(
            name="rb",
            subject=group_node,
            role=dual_role_viewer,
            restricted_object=root,
            propagate=True,
        )
        # Create a child AFTER the binding exists
        new_child = DualInheritanceModel.objects.create(name="new_child", parent=root)
        assert _has_perm(user1, PERM_VIEW, new_child)
        assert _has_perm(user2, PERM_VIEW, new_child)

    def test_moving_group_node_updates_propagated_perms(self, user1, dual_role_viewer, dual_perm_change):
        """
        When an instance (that is also a group subject target)
        is moved in the tree, its propagated permissions update.
        """
        root_a = DualInheritanceModel.objects.create(name="root_a")
        root_b = DualInheritanceModel.objects.create(name="root_b")
        node = DualInheritanceModel.objects.create(name="node", parent=root_a)

        group = DualInheritanceModel.objects.create(name="group")
        group.add_user(user1)

        dual_role_viewer.add_permission(dual_perm_change)
        RoleBinding.objects.create(
            name="rb_a",
            subject=group,
            role=dual_role_viewer,
            restricted_object=root_a,
            propagate=True,
        )
        assert _has_perm(user1, PERM_VIEW, node)

        # Move node to root_b (which has no binding)
        node.change_parent(root_b)
        assert not _has_perm(user1, PERM_VIEW, node)


# ---------------------------------------------------------------------------
# Tests: get_queryset_for_user on the dual-inheritance subclass
# ---------------------------------------------------------------------------


@pytest.mark.django_db
class TestGetQuerysetForUser:
    """
    Test DualInheritanceModel.get_queryset_for_user, which is a classmethod
    inherited from RestrictedObject but called on the subclass.
    The returned queryset must contain subclass instances, not bare
    RestrictedObject rows.
    """

    def test_returns_subclass_instances(self, user1, dual_role_viewer):
        """get_queryset_for_user on subclass returns subclass objects."""
        obj = DualInheritanceModel.objects.create(name="obj1")
        RoleBinding.objects.create(name="rb", subject=user1, role=dual_role_viewer, restricted_object=obj)
        qs = DualInheritanceModel.get_queryset_for_user(user1, PERM_VIEW)
        assert qs.count() == 1
        assert isinstance(qs.first(), DualInheritanceModel)

    def test_filters_to_permitted_only(self, user1, dual_role_viewer):
        """Only instances the user has permission on are returned."""
        obj_allowed = DualInheritanceModel.objects.create(name="allowed")
        DualInheritanceModel.objects.create(name="forbidden")
        RoleBinding.objects.create(
            name="rb",
            subject=user1,
            role=dual_role_viewer,
            restricted_object=obj_allowed,
        )
        qs = DualInheritanceModel.get_queryset_for_user(user1, PERM_VIEW)
        assert list(qs) == [obj_allowed]

    def test_superuser_sees_all(self):
        """Superuser gets all instances."""
        DualInheritanceModel.objects.create(name="obj1")
        DualInheritanceModel.objects.create(name="obj2")
        superuser = UserSubject.objects.create_superuser(username="admin", password="pass")
        qs = DualInheritanceModel.get_queryset_for_user(superuser, PERM_VIEW)
        assert qs.count() == 2

    def test_propagated_binding_includes_descendants(self, user1, dual_role_viewer):
        """Propagated binding on root includes child instances."""
        root = DualInheritanceModel.objects.create(name="root")
        child = DualInheritanceModel.objects.create(name="child", parent=root)
        RoleBinding.objects.create(
            name="rb",
            subject=user1,
            role=dual_role_viewer,
            restricted_object=root,
            propagate=True,
        )
        qs = DualInheritanceModel.get_queryset_for_user(user1, PERM_VIEW)
        assert set(qs) == {root, child}


# ---------------------------------------------------------------------------
# Tests: deletion semantics for dual-inheritance model
# ---------------------------------------------------------------------------


@pytest.mark.django_db
class TestDualInheritanceDeleteBehavior:
    """
    Test deletion semantics for the dual-inheritance model, which inherits
    delete() from RestrictedObject.  Children should be detached to root level.
    """

    def test_delete_detaches_children(self):
        """Deleting an instance detaches its children to root level."""
        root = DualInheritanceModel.objects.create(name="root")
        child1 = DualInheritanceModel.objects.create(name="child1", parent=root)
        child2 = DualInheritanceModel.objects.create(name="child2", parent=root)

        root.delete()

        child1.refresh_from_db()
        child2.refresh_from_db()
        assert child1.parent is None
        assert child2.parent is None
        assert child1.depth == 1
        assert child2.depth == 1

    def test_delete_cleans_propagated_perms(self):
        """
        Deleting a mid-tree instance detaches children and removes
        inherited permissions that came through the deleted node.
        """
        perm_view = Permission.objects.get(codename=PERM_VIEW)
        user = UserSubject.objects.create_user(username="u1", password="pass")
        role = Role.objects.create(name="viewer")
        role.add_permission(perm_view)

        root = DualInheritanceModel.objects.create(name="root")
        mid = DualInheritanceModel.objects.create(name="mid", parent=root)
        leaf = DualInheritanceModel.objects.create(name="leaf", parent=mid)

        RoleBinding.objects.create(
            name="rb",
            subject=user,
            role=role,
            restricted_object=root,
            propagate=True,
        )
        assert _has_perm(user, PERM_VIEW, leaf)

        mid.delete()

        leaf.refresh_from_db()
        # leaf is now a root; it lost inherited perms from root
        assert leaf.parent is None
        assert not _has_perm(user, PERM_VIEW, leaf)
