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

from unittest.mock import Mock, patch

from modules.interfaces.interfaceBootstrap import (
    initRootMandate,
    initAdminUser,
    initEventUser,
    initRbacRules,
    _createDefaultRoleRules,
    _createTableSpecificRules,
)
from modules.datamodels.datamodelUam import UserInDB, Mandate
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=[])
        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"
        assert callArgs[0][1].label == "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 isSysAdmin=True."""
        db = Mock()
        db.getRecordset = Mock(return_value=[])
        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 user.isSysAdmin is True

    def testInitEventUserCreatesWithSysadminRole(self):
        """Test that initEventUser creates user with isSysAdmin=True."""
        db = Mock()
        db.getRecordset = Mock(return_value=[])
        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 user.isSysAdmin is True

    def testCreateDefaultRoleRules(self):
        """Test that _createDefaultRoleRules creates admin + viewer generic DATA rules."""
        db = Mock()
        db.recordCreate = Mock()

        with patch(
            "modules.interfaces.interfaceBootstrap._getRoleId",
            side_effect=lambda d, label: f"rid-{label}",
        ):
            _createDefaultRoleRules(db)

        assert db.recordCreate.call_count == 2
        created = [c[0][1] for c in db.recordCreate.call_args_list]
        byRoleId = {r.roleId: r for r in created}

        adminRule = byRoleId["rid-admin"]
        assert adminRule.context == AccessRuleContext.DATA
        assert adminRule.item is None
        assert adminRule.view is True
        assert adminRule.read == AccessLevel.GROUP
        assert adminRule.create == AccessLevel.GROUP

        viewerRule = byRoleId["rid-viewer"]
        assert viewerRule.read == AccessLevel.GROUP
        assert viewerRule.create == AccessLevel.NONE

    def testCreateTableSpecificRules(self):
        """Test that _createTableSpecificRules creates table-specific rules."""
        db = Mock()
        db.recordCreate = Mock()

        with patch(
            "modules.interfaces.interfaceBootstrap._getRoleId",
            side_effect=lambda d, label: f"rid-{label}",
        ):
            _createTableSpecificRules(db)

        assert db.recordCreate.call_count > 0

        mandateCalls = [
            call for call in db.recordCreate.call_args_list if call[0][1].item == "data.uam.Mandate"
        ]
        assert len(mandateCalls) > 0

        for call in mandateCalls:
            rule = call[0][1]
            assert rule.view is False
            assert rule.read == AccessLevel.NONE

    def testInitRbacRulesSkipsIfExists(self):
        """When AccessRules already exist, init skips full init; ensure-* hooks are not exercised here."""
        db = Mock()
        db.getRecordset = Mock(return_value=[{"id": "existing_rule"}])
        db.recordCreate = Mock()

        with patch("modules.interfaces.interfaceBootstrap._ensureUiContextRules"):
            with patch("modules.interfaces.interfaceBootstrap._ensureDataContextRules"):
                initRbacRules(db)

        db.recordCreate.assert_not_called()

    def testInitRbacRulesCreatesIfNotExists(self):
        """Test that initRbacRules creates rules when the AccessRule table is empty."""
        db = Mock()
        db.recordCreate = Mock()
        db.recordModify = Mock()
        db.getRecordset = Mock(return_value=[])

        with patch(
            "modules.interfaces.interfaceBootstrap._getRoleId",
            side_effect=lambda d, label: f"rid-{label}",
        ):
            initRbacRules(db)

        assert db.recordCreate.call_count > 0
