# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Unit tests for RBAC permission resolution.
Tests rule specificity, multiple roles, and permission combination logic.
"""

import pytest
from modules.datamodels.datamodelUam import User, AccessLevel, UserPermissions
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
from modules.security.rbac import RbacClass
from modules.connectors.connectorDbPostgre import DatabaseConnector
from unittest.mock import Mock, MagicMock


class TestRbacPermissionResolution:
    """Test RBAC permission resolution logic."""
    
    def testSingleRoleGenericRule(self):
        """Test permission resolution with a single role and generic rule."""
        # Mock database connector
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        
        # Create RBAC interface
        rbac = RbacClass(db, dbApp=dbApp)
        
        # Create user with single role
        user = User(
            id="user1",
            username="testuser",
            roleLabels=["user"],
            mandateId="mandate1"
        )
        
        # Mock rules for "user" role
        def mockGetRulesForRole(roleLabel, context):
            if roleLabel == "user" and context == AccessRuleContext.DATA:
                return [
                    AccessRule(
                        roleLabel="user",
                        context=AccessRuleContext.DATA,
                        item=None,  # Generic rule
                        view=True,
                        read=AccessLevel.MY,
                        create=AccessLevel.MY,
                        update=AccessLevel.MY,
                        delete=AccessLevel.MY
                    )
                ]
            return []
        
        rbac._getRulesForRole = mockGetRulesForRole
        
        # Get permissions for generic table
        permissions = rbac.getUserPermissions(
            user,
            AccessRuleContext.DATA,
            "SomeTable"
        )
        
        assert permissions.view == True
        assert permissions.read == AccessLevel.MY
        assert permissions.create == AccessLevel.MY
        assert permissions.update == AccessLevel.MY
        assert permissions.delete == AccessLevel.MY
    
    def testRuleSpecificityMostSpecificWins(self):
        """Test that most specific rule wins within a single role."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        user = User(
            id="user1",
            username="testuser",
            roleLabels=["user"],
            mandateId="mandate1"
        )
        
        def mockGetRulesForRole(roleLabel, context):
            if roleLabel == "user" and context == AccessRuleContext.DATA:
                return [
                    AccessRule(
                        roleLabel="user",
                        context=AccessRuleContext.DATA,
                        item=None,  # Generic rule
                        view=True,
                        read=AccessLevel.GROUP,
                        create=AccessLevel.GROUP,
                        update=AccessLevel.GROUP,
                        delete=AccessLevel.GROUP
                    ),
                    AccessRule(
                        roleLabel="user",
                        context=AccessRuleContext.DATA,
                        item="data.uam.UserInDB",  # Specific rule with UAM namespace
                        view=True,
                        read=AccessLevel.MY,
                        create=AccessLevel.NONE,
                        update=AccessLevel.MY,
                        delete=AccessLevel.NONE
                    )
                ]
            return []
        
        rbac._getRulesForRole = mockGetRulesForRole
        
        # Get permissions for UserInDB table - should use specific rule
        # Using UAM namespace: data.uam.UserInDB
        permissions = rbac.getUserPermissions(
            user,
            AccessRuleContext.DATA,
            "data.uam.UserInDB"
        )
        
        # Most specific rule should win
        assert permissions.read == AccessLevel.MY
        assert permissions.create == AccessLevel.NONE
        assert permissions.update == AccessLevel.MY
        assert permissions.delete == AccessLevel.NONE
    
    def testMultipleRolesUnionLogic(self):
        """Test that multiple roles use union (opening) logic."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        # User with multiple roles
        user = User(
            id="user1",
            username="testuser",
            roleLabels=["user", "viewer"],
            mandateId="mandate1"
        )
        
        def mockGetRulesForRole(roleLabel, context):
            if context == AccessRuleContext.UI:
                if roleLabel == "user":
                    return [
                        AccessRule(
                            roleLabel="user",
                            context=AccessRuleContext.UI,
                            item="playground",
                            view=False  # User role hides playground
                        )
                    ]
                elif roleLabel == "viewer":
                    return [
                        AccessRule(
                            roleLabel="viewer",
                            context=AccessRuleContext.UI,
                            item="playground",
                            view=True  # Viewer role shows playground
                        )
                    ]
            return []
        
        rbac._getRulesForRole = mockGetRulesForRole
        
        # Get permissions - union logic should make playground visible
        permissions = rbac.getUserPermissions(
            user,
            AccessRuleContext.UI,
            "playground"
        )
        
        # Union logic: if ANY role has view=true, then view=true
        assert permissions.view == True
    
    def testViewFalseOverridesGeneric(self):
        """Test that specific view=false overrides generic view=true."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        user = User(
            id="user1",
            username="testuser",
            roleLabels=["user"],
            mandateId="mandate1"
        )
        
        def mockGetRulesForRole(roleLabel, context):
            if roleLabel == "user" and context == AccessRuleContext.UI:
                return [
                    AccessRule(
                        roleLabel="user",
                        context=AccessRuleContext.UI,
                        item=None,  # Generic: view all UI
                        view=True
                    ),
                    AccessRule(
                        roleLabel="user",
                        context=AccessRuleContext.UI,
                        item="playground.voice.settings",  # Specific: hide this
                        view=False
                    )
                ]
            return []
        
        rbac._getRulesForRole = mockGetRulesForRole
        
        # Get permissions for specific UI element
        permissions = rbac.getUserPermissions(
            user,
            AccessRuleContext.UI,
            "playground.voice.settings"
        )
        
        # Specific rule should override generic
        assert permissions.view == False
    
    def testNoRolesReturnsNoAccess(self):
        """Test that user with no roles gets no access."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        user = User(
            id="user1",
            username="testuser",
            roleLabels=[],  # No roles
            mandateId="mandate1"
        )
        
        permissions = rbac.getUserPermissions(
            user,
            AccessRuleContext.DATA,
            "SomeTable"
        )
        
        assert permissions.view == False
        assert permissions.read == AccessLevel.NONE
        assert permissions.create == AccessLevel.NONE
        assert permissions.update == AccessLevel.NONE
        assert permissions.delete == AccessLevel.NONE
    
    def testFindMostSpecificRule(self):
        """Test findMostSpecificRule method."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        rules = [
            AccessRule(
                roleLabel="user",
                context=AccessRuleContext.DATA,
                item=None,  # Generic
                view=True,
                read=AccessLevel.GROUP
            ),
            AccessRule(
                roleLabel="user",
                context=AccessRuleContext.DATA,
                item="data.uam.UserInDB",  # Table-level with UAM namespace
                view=True,
                read=AccessLevel.MY
            ),
            AccessRule(
                roleLabel="user",
                context=AccessRuleContext.DATA,
                item="data.uam.UserInDB.email",  # Field-level - most specific
                view=True,
                read=AccessLevel.NONE
            )
        ]
        
        # Test exact match
        rule = rbac.findMostSpecificRule(rules, "data.uam.UserInDB.email")
        assert rule is not None
        assert rule.item == "data.uam.UserInDB.email"
        assert rule.read == AccessLevel.NONE
        
        # Test table-level match
        rule = rbac.findMostSpecificRule(rules, "data.uam.UserInDB")
        assert rule is not None
        assert rule.item == "data.uam.UserInDB"
        assert rule.read == AccessLevel.MY
        
        # Test generic fallback
        rule = rbac.findMostSpecificRule(rules, "OtherTable")
        assert rule is not None
        assert rule.item is None
        assert rule.read == AccessLevel.GROUP
    
    def testValidateAccessRuleOpeningRights(self):
        """Test that CUD permissions respect read permission level."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        # Valid: Read=MY, Create=MY (allowed)
        rule1 = AccessRule(
            roleLabel="user",
            context=AccessRuleContext.DATA,
            item="data.uam.UserInDB",
            view=True,
            read=AccessLevel.MY,
            create=AccessLevel.MY,
            update=AccessLevel.MY,
            delete=AccessLevel.MY
        )
        assert rbac.validateAccessRule(rule1) == True
        
        # Invalid: Read=MY, Create=GROUP (not allowed - GROUP > MY)
        rule2 = AccessRule(
            roleLabel="user",
            context=AccessRuleContext.DATA,
            item="data.uam.UserInDB",
            view=True,
            read=AccessLevel.MY,
            create=AccessLevel.GROUP,  # Not allowed
            update=AccessLevel.MY,
            delete=AccessLevel.MY
        )
        assert rbac.validateAccessRule(rule2) == False
        
        # Valid: Read=GROUP, Create=GROUP (allowed)
        rule3 = AccessRule(
            roleLabel="admin",
            context=AccessRuleContext.DATA,
            item="data.uam.UserInDB",
            view=True,
            read=AccessLevel.GROUP,
            create=AccessLevel.GROUP,
            update=AccessLevel.GROUP,
            delete=AccessLevel.GROUP
        )
        assert rbac.validateAccessRule(rule3) == True
        
        # Invalid: Read=NONE, Create=MY (not allowed - no read access)
        rule4 = AccessRule(
            roleLabel="user",
            context=AccessRuleContext.DATA,
            item="data.uam.UserInDB",
            view=True,
            read=AccessLevel.NONE,
            create=AccessLevel.MY,  # Not allowed without read
            update=AccessLevel.MY,
            delete=AccessLevel.MY
        )
        assert rbac.validateAccessRule(rule4) == False
    
    def testUiContextOnlyViewMatters(self):
        """Test that UI context only checks view permission."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        user = User(
            id="user1",
            username="testuser",
            roleLabels=["user"],
            mandateId="mandate1"
        )
        
        def mockGetRulesForRole(roleLabel, context):
            if roleLabel == "user" and context == AccessRuleContext.UI:
                return [
                    AccessRule(
                        roleLabel="user",
                        context=AccessRuleContext.UI,
                        item="playground",
                        view=True
                        # No read/create/update/delete for UI context
                    )
                ]
            return []
        
        rbac._getRulesForRole = mockGetRulesForRole
        
        permissions = rbac.getUserPermissions(
            user,
            AccessRuleContext.UI,
            "playground"
        )
        
        assert permissions.view == True
        # Other permissions don't matter for UI context
    
    def testResourceContextOnlyViewMatters(self):
        """Test that RESOURCE context only checks view permission."""
        db = Mock(spec=DatabaseConnector)
        dbApp = Mock(spec=DatabaseConnector)
        rbac = RbacClass(db, dbApp=dbApp)
        
        user = User(
            id="user1",
            username="testuser",
            roleLabels=["user"],
            mandateId="mandate1"
        )
        
        def mockGetRulesForRole(roleLabel, context):
            if roleLabel == "user" and context == AccessRuleContext.RESOURCE:
                return [
                    AccessRule(
                        roleLabel="user",
                        context=AccessRuleContext.RESOURCE,
                        item="ai.model.anthropic",
                        view=True
                    )
                ]
            return []
        
        rbac._getRulesForRole = mockGetRulesForRole
        
        permissions = rbac.getUserPermissions(
            user,
            AccessRuleContext.RESOURCE,
            "ai.model.anthropic"
        )
        
        assert permissions.view == True
