"""
Base test classes and utilities for topdownrbac tests.

Provides common setup, fixtures, and helper methods used across test modules.
"""
from django.contrib.auth.models import Permission
from django.test import TestCase

from topdownrbac.models import RestrictedObject, Role, UserGroupSubject, UserSubject


class BaseRBACTestCase(TestCase):
    """
    Base test class providing common RBAC fixtures and helper methods.

    Fixtures created:
        - Permissions: view, change, delete for RestrictedObject
        - Users: user1, user2
        - Roles: role1 (empty), role2 (empty)
        - RestrictedObjects: obj1, obj2 (flat, no hierarchy)
    """

    @classmethod
    def setUpTestData(cls):
        """Load permissions once for all tests in the class."""
        cls.perm_view = Permission.objects.get(codename='view_restrictedobject')
        cls.perm_change = Permission.objects.get(codename='change_restrictedobject')
        cls.perm_delete = Permission.objects.get(codename='delete_restrictedobject')

    def setUp(self):
        """Create fresh users, roles, and objects for each test."""
        self.user1 = UserSubject.objects.create_user(username='user1', password='pass')
        self.user2 = UserSubject.objects.create_user(username='user2', password='pass')

        self.role1 = Role.objects.create(name='role1')
        self.role2 = Role.objects.create(name='role2')

        self.obj1 = RestrictedObject.objects.create()
        self.obj2 = RestrictedObject.objects.create()

    # -------------------------------------------------------------------------
    # Permission Assertion Helpers
    # -------------------------------------------------------------------------

    def assert_has_perm(self, user, perm_codename, obj):
        """Assert that user has the given permission on obj."""
        self.assertTrue(
            user.has_perm(perm_codename, obj=obj),
            f"{user.username} should have '{perm_codename}' on {obj}"
        )

    def assert_no_perm(self, user, perm_codename, obj):
        """Assert that user does NOT have the given permission on obj."""
        self.assertFalse(
            user.has_perm(perm_codename, obj=obj),
            f"{user.username} should NOT have '{perm_codename}' on {obj}"
        )

    def assert_has_view(self, user, obj):
        """Assert user has view permission on obj."""
        self.assert_has_perm(user, 'view_restrictedobject', obj)

    def assert_no_view(self, user, obj):
        """Assert user does NOT have view permission on obj."""
        self.assert_no_perm(user, 'view_restrictedobject', obj)

    def assert_has_change(self, user, obj):
        """Assert user has change permission on obj."""
        self.assert_has_perm(user, 'change_restrictedobject', obj)

    def assert_no_change(self, user, obj):
        """Assert user does NOT have change permission on obj."""
        self.assert_no_perm(user, 'change_restrictedobject', obj)

    def assert_has_delete(self, user, obj):
        """Assert user has delete permission on obj."""
        self.assert_has_perm(user, 'delete_restrictedobject', obj)

    def assert_no_delete(self, user, obj):
        """Assert user does NOT have delete permission on obj."""
        self.assert_no_perm(user, 'delete_restrictedobject', obj)

    def assert_has_view_and_change(self, user, obj):
        """Assert user has both view and change permissions on obj."""
        self.assert_has_view(user, obj)
        self.assert_has_change(user, obj)

    def assert_no_view_and_change(self, user, obj):
        """Assert user has neither view nor change permissions on obj."""
        self.assert_no_view(user, obj)
        self.assert_no_change(user, obj)

    def assert_perms_on_objects(self, user, perm_codename, objects, expected):
        """
        Assert permission state on multiple objects.

        Args:
            user: The UserSubject to check
            perm_codename: Permission codename (e.g., 'view_restrictedobject')
            objects: List of RestrictedObject instances
            expected: True if user should have perm, False otherwise
        """
        for obj in objects:
            if expected:
                self.assert_has_perm(user, perm_codename, obj)
            else:
                self.assert_no_perm(user, perm_codename, obj)


class BaseHierarchyTestCase(BaseRBACTestCase):
    """
    Base test class with a hierarchical RestrictedObject structure.

    Hierarchy:
        obj1 (root)
        ├── obj2
        │   ├── obj4
        │   └── obj5
        └── obj3
    """

    def setUp(self):
        super().setUp()
        # Override flat structure with hierarchy
        self.obj1 = RestrictedObject.objects.create()
        self.obj2 = RestrictedObject.objects.create(parent=self.obj1)
        self.obj3 = RestrictedObject.objects.create(parent=self.obj1)
        self.obj4 = RestrictedObject.objects.create(parent=self.obj2)
        self.obj5 = RestrictedObject.objects.create(parent=self.obj2)

    @property
    def all_objects(self):
        """Return all objects in hierarchy."""
        return [self.obj1, self.obj2, self.obj3, self.obj4, self.obj5]

    @property
    def obj2_subtree(self):
        """Return obj2 and its descendants."""
        return [self.obj2, self.obj4, self.obj5]


class BaseUserGroupTestCase(BaseHierarchyTestCase):
    """
    Base test class with UserGroupSubject fixtures.

    Extends hierarchy test case with:
        - group1: contains user1, user2
        - group2: contains user3
        - user3: additional user in group2
    """

    def setUp(self):
        super().setUp()
        self.user3 = UserSubject.objects.create_user(username='user3', password='pass')

        self.group1 = UserGroupSubject.objects.create()
        self.group1.add_user(self.user1)
        self.group1.add_user(self.user2)

        self.group2 = UserGroupSubject.objects.create()
        self.group2.add_user(self.user3)

    @property
    def group1_users(self):
        """Return users in group1."""
        return [self.user1, self.user2]
