# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Unit tests for RBAC bootstrap initialization.
Tests that bootstrap creates correct rules and initial data.
"""

import pytest
from unittest.mock import Mock, MagicMock, patch
from modules.interfaces.interfaceBootstrap import (
    initBootstrap,
    initRootMandate,
    initAdminUser,
    initEventUser,
    initRbacRules,
    createDefaultRoleRules,
    createTableSpecificRules
)
from modules.datamodels.datamodelUam import UserInDB, Mandate, AuthAuthority
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
from modules.datamodels.datamodelUam import AccessLevel


class TestRbacBootstrap:
    """Test RBAC bootstrap initialization."""
    
    def testInitRootMandateCreatesIfNotExists(self):
        """Test that initRootMandate creates mandate if it doesn't exist."""
        db = Mock()
        db.getRecordset = Mock(return_value=[])  # No existing mandates
        db.recordCreate = Mock(return_value={"id": "mandate1", "name": "Root"})
        
        mandateId = initRootMandate(db)
        
        assert mandateId == "mandate1"
        db.recordCreate.assert_called_once()
        callArgs = db.recordCreate.call_args
        assert isinstance(callArgs[0][1], Mandate)
        assert callArgs[0][1].name == "Root"
    
    def testInitRootMandateReturnsExisting(self):
        """Test that initRootMandate returns existing mandate ID."""
        db = Mock()
        db.getRecordset = Mock(return_value=[{"id": "existing_mandate"}])
        
        mandateId = initRootMandate(db)
        
        assert mandateId == "existing_mandate"
        db.recordCreate.assert_not_called()
    
    def testInitAdminUserCreatesWithSysadminRole(self):
        """Test that initAdminUser creates user with sysadmin role."""
        db = Mock()
        db.getRecordset = Mock(return_value=[])  # No existing users
        db.recordCreate = Mock(return_value={"id": "admin1", "username": "admin"})
        
        with patch('modules.interfaces.interfaceBootstrap._getPasswordHash', return_value="hashed"):
            userId = initAdminUser(db, "mandate1")
        
        assert userId == "admin1"
        db.recordCreate.assert_called_once()
        callArgs = db.recordCreate.call_args
        user = callArgs[0][1]
        assert isinstance(user, UserInDB)
        assert user.username == "admin"
        assert "sysadmin" in user.roleLabels
    
    def testInitEventUserCreatesWithSysadminRole(self):
        """Test that initEventUser creates user with sysadmin role."""
        db = Mock()
        db.getRecordset = Mock(return_value=[])  # No existing users
        db.recordCreate = Mock(return_value={"id": "event1", "username": "event"})
        
        with patch('modules.interfaces.interfaceBootstrap._getPasswordHash', return_value="hashed"):
            userId = initEventUser(db, "mandate1")
        
        assert userId == "event1"
        db.recordCreate.assert_called_once()
        callArgs = db.recordCreate.call_args
        user = callArgs[0][1]
        assert isinstance(user, UserInDB)
        assert user.username == "event"
        assert "sysadmin" in user.roleLabels
    
    def testCreateDefaultRoleRules(self):
        """Test that createDefaultRoleRules creates correct default rules."""
        db = Mock()
        db.recordCreate = Mock()
        
        createDefaultRoleRules(db)
        
        # Should create 4 default rules (sysadmin, admin, user, viewer)
        assert db.recordCreate.call_count == 4
        
        # Check sysadmin rule
        sysadminCall = [call for call in db.recordCreate.call_args_list 
                       if call[0][1].roleLabel == "sysadmin"][0]
        sysadminRule = sysadminCall[0][1]
        assert sysadminRule.context == AccessRuleContext.DATA
        assert sysadminRule.item is None
        assert sysadminRule.view == True
        assert sysadminRule.read == AccessLevel.ALL
        assert sysadminRule.create == AccessLevel.ALL
        
        # Check user rule
        userCall = [call for call in db.recordCreate.call_args_list 
                   if call[0][1].roleLabel == "user"][0]
        userRule = userCall[0][1]
        assert userRule.read == AccessLevel.MY
        assert userRule.create == AccessLevel.MY
    
    def testCreateTableSpecificRules(self):
        """Test that createTableSpecificRules creates table-specific rules."""
        db = Mock()
        db.recordCreate = Mock()
        
        createTableSpecificRules(db)
        
        # Should create multiple rules for different tables
        assert db.recordCreate.call_count > 0
        
        # Check that Mandate table rules are created with full objectKey (UAM namespace)
        mandateCalls = [call for call in db.recordCreate.call_args_list 
                        if call[0][1].item == "data.uam.Mandate"]
        assert len(mandateCalls) > 0
        
        # Check that all roles have view=False and no access for Mandate
        # (SysAdmin bypasses RBAC via isSysAdmin flag, not via roles)
        for call in mandateCalls:
            rule = call[0][1]
            assert rule.view == False
            assert rule.read == AccessLevel.NONE
    
    def testInitRbacRulesSkipsIfExists(self):
        """Test that initRbacRules skips default rule creation if rules already exist, but adds missing table-specific rules."""
        db = Mock()
        # Mock existing rules - include rules for ChatWorkflow and AutomationDefinition to prevent adding missing rules
        # Need rules for all required roles to fully prevent creation
        # Using semantic namespace format: data.chat.{TableName}, data.automation.{TableName}
        existingRules = []
        for table in ["data.chat.ChatWorkflow", "data.automation.AutomationDefinition"]:
            for role in ["admin", "user", "viewer"]:
                existingRules.append({
                    "id": f"rule_{table}_{role}",
                    "item": table,
                    "context": AccessRuleContext.DATA.value,
                    "roleLabel": role
                })
        db.getRecordset = Mock(return_value=existingRules)
        db.recordCreate = Mock()
        
        initRbacRules(db)
        
        # Should not create new rules since all required tables already have rules for all roles
        db.recordCreate.assert_not_called()
    
    def testInitRbacRulesCreatesIfNotExists(self):
        """Test that initRbacRules creates rules if they don't exist."""
        db = Mock()
        db.getRecordset = Mock(side_effect=[
            [],  # No existing rules
            []   # After creating default rules
        ])
        db.recordCreate = Mock()
        
        initRbacRules(db)
        
        # Should create rules
        assert db.recordCreate.call_count > 0
