package approvalapp

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/service/role/execution"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
)

func TestRoleManagementPendingCardProjectsExactHumanSafeFieldMatrix(t *testing.T) {
	name, description, clearDescription, definition := "Release reviewer", "Own releases", "", "Review every release"
	instance := approval.Instance{
		ID: "appr_role_matrix", WorkspaceID: "ws_secret", Version: 1,
		PayloadDigest: "digest-secret", ApplicantMemberID: "principal-secret",
		DecidedByMemberID: "manager-secret", CorrelationID: "raw-upstream-secret",
	}
	tests := []struct {
		name    string
		payload execution.ApprovalPayload
		summary map[string]any
	}{
		{
			name: "create shows all requested role content",
			payload: execution.ApprovalPayload{
				Action: execution.ActionCreate,
				Create: &execution.RoleCreateProposal{
					Name: name, Description: description, Definition: definition,
				},
			},
			summary: map[string]any{
				"name": name, "description": description, "definition": definition,
			},
		},
		{
			name: "update shows all present fields and preserves explicit clear",
			payload: execution.ApprovalPayload{
				Action: execution.ActionUpdate,
				Update: &execution.RoleUpdateProposal{
					RoleID: "role_update", ExpectedRoleVersionID: "rlv_expected",
					Name: &name, Description: &clearDescription,
					Definition: &definition, Visibility: "workspace",
				},
			},
			summary: map[string]any{
				"roleId": "role_update", "expectedRoleVersionId": "rlv_expected",
				"name": name, "description": "", "definition": definition,
				"visibility": "workspace",
			},
		},
		{
			name: "update omits every absent optional field",
			payload: execution.ApprovalPayload{
				Action: execution.ActionUpdate,
				Update: &execution.RoleUpdateProposal{
					RoleID: "role_update_name", ExpectedRoleVersionID: "rlv_name",
					Name: &name,
				},
			},
			summary: map[string]any{
				"roleId": "role_update_name", "expectedRoleVersionId": "rlv_name",
				"name": name,
			},
		},
		{
			name: "delete shows the target and stale-write fence",
			payload: execution.ApprovalPayload{
				Action: execution.ActionDelete,
				Delete: &execution.RoleDeleteProposal{
					RoleID: "role_delete", ExpectedRoleVersionID: "rlv_delete",
				},
			},
			summary: map[string]any{
				"roleId": "role_delete", "expectedRoleVersionId": "rlv_delete",
			},
		},
		{
			name: "apply shows immutable role version and target agent",
			payload: execution.ApprovalPayload{
				Action: execution.ActionApply,
				Apply: &execution.RoleApplyProposal{
					RoleID: "role_apply", RoleVersionID: "rlv_apply", AgentID: "agt_target",
				},
			},
			summary: map[string]any{
				"roleId": "role_apply", "roleVersionId": "rlv_apply", "agentId": "agt_target",
			},
		},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			testCase.payload.SchemaVersion = execution.PayloadSchemaVersion
			testCase.payload.RequestID = "lease-secret"
			testCase.payload.ApplicantAgentID = "machine-secret"

			content, err := roleManagementCardContent(
				instance, "asg_role_matrix", "mem_manager",
				testCase.payload, approval.StatePending,
			)
			require.NoError(t, err)
			var card map[string]any
			require.NoError(t, json.Unmarshal(content.CardJSON, &card))
			require.Equal(t, map[string]any{
				"action": string(testCase.payload.Action), "summary": testCase.summary,
			}, card["proposal"])
			require.Len(t, card["actions"], 2)
			for _, secret := range []string{
				"digest-secret", "principal-secret", "manager-secret",
				"machine-secret", "lease-secret", "raw-upstream-secret",
			} {
				require.NotContains(t, string(content.CardJSON), secret)
			}
			for _, forbiddenKey := range []string{
				"digest", "principal", "manager", "machine", "lease", "fence",
				"raw", "upstream", "error",
			} {
				require.False(t, roleCardContainsKey(card, forbiddenKey),
					"pending Human card must not expose %q", forbiddenKey)
			}
		})
	}
}

func TestRoleManagementTerminalObserverConvergesSameProcessingCard(t *testing.T) {
	fixture := newRoleManagementTerminalFixture(t, "converges")

	require.NoError(t, fixture.observer.ProjectTerminal(
		fixture.ctx, fixture.instance, fixture.success,
	))
	require.Equal(t, 1, fixture.notifications.mutationCalls)
	require.Equal(t, app.AppNotificationMutationResolve, fixture.notifications.mutated.Kind)
	require.Equal(t, fixture.notificationID, fixture.notifications.notification.ID)
	require.Equal(t, fixture.messageID, fixture.notifications.notification.MessageID)
	require.Equal(t, app.AppNotificationStatusResolved, fixture.notifications.notification.Status)
	require.Equal(t, app.AppNotificationCardStateResolved, fixture.notifications.card.State)

	var card map[string]any
	require.NoError(t, json.Unmarshal(fixture.notifications.card.RenderPayloadJSON, &card))
	require.Equal(t, string(execution.RequestSucceeded), card["status"])
	require.Empty(t, card["actions"])
	require.Equal(t, map[string]any{
		"roleId": "role_result", "roleVersionId": "rlv_result",
	}, card["result"])
	require.Equal(t, map[string]any{
		"action": string(execution.ActionCreate),
		"summary": map[string]any{
			"name": "Release reviewer", "description": "Own releases",
			"definition": "Review every release",
		},
	}, card["proposal"])

	require.NoError(t, fixture.observer.ProjectTerminal(
		fixture.ctx, fixture.instance, fixture.success,
	))
	require.Equal(t, 1, fixture.notifications.mutationCalls,
		"fully equivalent terminal content must not remutate the same card")
}

func TestRoleManagementTerminalObserverProjectsOnlySafeDeterministicFailure(t *testing.T) {
	fixture := newRoleManagementTerminalFixture(t, "safe_failure")
	baseNotification := fixture.notifications.notification
	baseCard := fixture.notifications.card
	failures := []struct {
		code    execution.ApplyOutcome
		message string
	}{
		{
			code:    execution.ApplyInvalidRequest,
			message: "The approved role request is invalid.",
		},
		{
			code:    execution.ApplyAuthorityDenied,
			message: "The role operation is no longer authorized.",
		},
		{
			code:    execution.ApplyNotFound,
			message: "The requested role resource was not found.",
		},
		{
			code:    execution.ApplyVersionConflict,
			message: "The role changed before this request was applied.",
		},
		{
			code:    execution.ApplyOperationConflict,
			message: "A conflicting role operation already exists.",
		},
	}

	for _, testCase := range failures {
		t.Run(string(testCase.code), func(t *testing.T) {
			fixture.notifications.notification = baseNotification
			fixture.notifications.card = baseCard
			fixture.notifications.mutationCalls = 0
			failure := fixture.success
			failure.Status = execution.RequestFailed
			failure.RoleID = ""
			failure.RoleVersionID = ""
			failure.FailureCode = string(testCase.code)
			failure.FailureMessage = testCase.message

			require.NoError(t, fixture.observer.ProjectTerminal(
				fixture.ctx, fixture.instance, failure,
			))
			require.Equal(t, 1, fixture.notifications.mutationCalls)
			var card map[string]any
			require.NoError(t, json.Unmarshal(
				fixture.notifications.card.RenderPayloadJSON, &card,
			))
			require.Equal(t, string(execution.RequestFailed), card["status"])
			require.Equal(t, map[string]any{
				"failureCode": string(testCase.code), "failureMessage": testCase.message,
			}, card["result"])
			for _, secret := range []string{
				fixture.instance.PayloadDigest,
				fixture.instance.ApplicantMemberID,
				fixture.instance.DecidedByMemberID,
				fixture.instance.CorrelationID,
				"machine-secret", "lease-secret", "fence-secret", "raw-upstream-secret",
			} {
				require.NotContains(
					t, string(fixture.notifications.card.RenderPayloadJSON), secret,
				)
			}
			for _, forbiddenKey := range []string{
				"digest", "principal", "manager", "machine", "lease", "fence",
				"raw", "upstream", "error",
			} {
				require.False(t, roleCardContainsKey(card, forbiddenKey),
					"terminal Human card must not expose %q", forbiddenKey)
			}
		})
	}

	for _, testCase := range []struct {
		name    string
		code    string
		message string
	}{
		{
			name: "unknown code",
			code: "future_failure", message: "A future stable message.",
		},
		{
			name:    "raw message",
			code:    string(execution.ApplyNotFound),
			message: "raw upstream secret: credential=hidden",
		},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			fixture.notifications.notification = baseNotification
			fixture.notifications.card = baseCard
			fixture.notifications.mutationCalls = 0
			failure := fixture.success
			failure.Status = execution.RequestFailed
			failure.RoleID = ""
			failure.RoleVersionID = ""
			failure.FailureCode = testCase.code
			failure.FailureMessage = testCase.message

			require.Error(t, fixture.observer.ProjectTerminal(
				fixture.ctx, fixture.instance, failure,
			))
			require.Zero(t, fixture.notifications.mutationCalls,
				"unsafe failure must be rejected before notification mutation")
		})
	}
}

func TestRoleManagementTerminalSuccessCardResultMatrix(t *testing.T) {
	tests := []struct {
		name    string
		payload execution.ApprovalPayload
		result  execution.RequestResult
		want    map[string]any
	}{
		{
			name: "create",
			payload: execution.ApprovalPayload{
				Action: execution.ActionCreate,
				Create: &execution.RoleCreateProposal{
					Name: "Release", Definition: "Review",
				},
			},
			result: execution.RequestResult{
				Action: execution.ActionCreate,
				RoleID: "role_create", RoleVersionID: "rlv_create",
			},
			want: map[string]any{
				"roleId": "role_create", "roleVersionId": "rlv_create",
			},
		},
		{
			name: "update",
			payload: execution.ApprovalPayload{
				Action: execution.ActionUpdate,
				Update: &execution.RoleUpdateProposal{
					RoleID: "role_update", ExpectedRoleVersionID: "rlv_old",
					Visibility: "workspace",
				},
			},
			result: execution.RequestResult{
				Action: execution.ActionUpdate,
				RoleID: "role_update", RoleVersionID: "rlv_update",
			},
			want: map[string]any{
				"roleId": "role_update", "roleVersionId": "rlv_update",
			},
		},
		{
			name: "delete",
			payload: execution.ApprovalPayload{
				Action: execution.ActionDelete,
				Delete: &execution.RoleDeleteProposal{
					RoleID: "role_delete", ExpectedRoleVersionID: "rlv_delete",
				},
			},
			result: execution.RequestResult{
				Action: execution.ActionDelete,
				RoleID: "role_delete", RoleVersionID: "rlv_deleted",
			},
			want: map[string]any{
				"roleId": "role_delete", "roleVersionId": "rlv_deleted",
			},
		},
		{
			name: "apply preserves exact target agent",
			payload: execution.ApprovalPayload{
				Action: execution.ActionApply,
				Apply: &execution.RoleApplyProposal{
					RoleID: "role_apply", RoleVersionID: "rlv_apply",
					AgentID: "agt_exact_target",
				},
			},
			result: execution.RequestResult{
				Action: execution.ActionApply,
				RoleID: "role_apply", RoleVersionID: "rlv_apply",
				AgentID: "agt_exact_target",
			},
			want: map[string]any{
				"roleId": "role_apply", "roleVersionId": "rlv_apply",
				"agentId": "agt_exact_target",
			},
		},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			instance := approval.Instance{ID: "appr_matrix", Version: 2}
			testCase.payload.SchemaVersion = execution.PayloadSchemaVersion
			testCase.payload.RequestID = "req_matrix"
			testCase.payload.ApplicantAgentID = "agt_applicant"
			testCase.result.ApprovalID = instance.ID
			testCase.result.ApplicantAgentID = testCase.payload.ApplicantAgentID
			testCase.result.RequestID = testCase.payload.RequestID
			testCase.result.Status = execution.RequestSucceeded

			content, err := roleManagementTerminalCardContent(
				instance, "asg_matrix", testCase.payload, testCase.result,
			)
			require.NoError(t, err)
			var card map[string]any
			require.NoError(t, json.Unmarshal(content.CardJSON, &card))
			require.Equal(t, testCase.want, card["result"])
			if testCase.payload.Action == execution.ActionApply {
				require.Equal(t, "agt_exact_target",
					card["result"].(map[string]any)["agentId"])
			} else {
				require.NotContains(t, card["result"].(map[string]any), "agentId")
			}
		})
	}
}

func TestRoleManagementTerminalObserverAuthorityMismatchDoesNotMutate(t *testing.T) {
	fixture := newRoleManagementTerminalFixture(t, "authority")
	baseInstance := fixture.instance
	baseNotification := fixture.notifications.notification
	baseCard := fixture.notifications.card
	tests := []struct {
		name   string
		mutate func(*roleManagementTerminalFixture) func()
	}{
		{
			name: "approval",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				fixture.instance.SubjectType = "other"
				return func() {}
			},
		},
		{
			name: "notification business object",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				namespace := "wrong.namespace"
				fixture.notifications.notification.BusinessObjectNamespace = &namespace
				return func() {}
			},
		},
		{
			name: "card record identity",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				fixture.notifications.card.AppNotificationID = "btn_wrong"
				return func() {}
			},
		},
		{
			name: "card snapshot approval id",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				fixture.notifications.card.RenderPayloadJSON = replaceRoleCardValue(
					t, fixture.notifications.card.RenderPayloadJSON,
					fixture.instance.ID, "appr_wrong",
				)
				return func() {}
			},
		},
		{
			name: "card snapshot approval version",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				fixture.notifications.card.RenderPayloadJSON = replaceRoleCardValue(
					t, fixture.notifications.card.RenderPayloadJSON,
					`"version":2`, `"version":3`,
				)
				return func() {}
			},
		},
		{
			name: "card snapshot assignment id",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				fixture.notifications.card.RenderPayloadJSON = replaceRoleCardValue(
					t, fixture.notifications.card.RenderPayloadJSON,
					fixture.assignment.ID, "apasg_wrong",
				)
				return func() {}
			},
		},
		{
			name: "notification assignment recipient",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				recipient := "mem_approval_bot_reader"
				fixture.notifications.notification.RecipientMemberID = &recipient
				return func() {}
			},
		},
		{
			name: "db assignment status",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				mustExec(t, fixture.db, `
UPDATE approval_assignments SET status='pending'
WHERE workspace_id=$1 AND id=$2
`, fixture.instance.WorkspaceID, fixture.assignment.ID)
				return func() {
					mustExec(t, fixture.db, `
UPDATE approval_assignments SET status='approved'
WHERE workspace_id=$1 AND id=$2
`, fixture.instance.WorkspaceID, fixture.assignment.ID)
				}
			},
		},
		{
			name: "db assignment authority",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				mustExec(t, fixture.db, `
UPDATE approval_assignments SET approver_member_id='mem_approval_bot_reader'
WHERE workspace_id=$1 AND id=$2
`, fixture.instance.WorkspaceID, fixture.assignment.ID)
				return func() {
					mustExec(t, fixture.db, `
UPDATE approval_assignments SET approver_member_id=$3
WHERE workspace_id=$1 AND id=$2
`, fixture.instance.WorkspaceID, fixture.assignment.ID,
						fixture.assignment.ApproverMemberID)
				}
			},
		},
		{
			name: "coordinated assignment and notification recipient redirect",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				otherApprover := "mem_approval_bot_reader"
				mustExec(t, fixture.db, `
UPDATE approval_assignments SET approver_member_id=$3
WHERE workspace_id=$1 AND id=$2
`, fixture.instance.WorkspaceID, fixture.assignment.ID, otherApprover)
				fixture.notifications.notification.RecipientMemberID = &otherApprover
				return func() {
					mustExec(t, fixture.db, `
UPDATE approval_assignments SET approver_member_id=$3
WHERE workspace_id=$1 AND id=$2
`, fixture.instance.WorkspaceID, fixture.assignment.ID,
						fixture.assignment.ApproverMemberID)
				}
			},
		},
		{
			name: "notification business object version",
			mutate: func(fixture *roleManagementTerminalFixture) func() {
				version := fixture.instance.Version - 1
				fixture.notifications.notification.BusinessObjectVersion = &version
				return func() {}
			},
		},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			fixture.instance = baseInstance
			fixture.notifications.notification = baseNotification
			fixture.notifications.card = baseCard
			fixture.notifications.mutationCalls = 0
			restore := testCase.mutate(fixture)
			defer restore()

			require.Error(t, fixture.observer.ProjectTerminal(
				fixture.ctx, fixture.instance, fixture.success,
			))
			require.Zero(t, fixture.notifications.mutationCalls,
				"authority mismatch must fail before notification mutation")
		})
	}
}

func TestRoleManagementTerminalObserverRejectsResolvedContentMismatch(t *testing.T) {
	fixture := newRoleManagementTerminalFixture(t, "resolved_mismatch")
	require.NoError(t, fixture.observer.ProjectTerminal(
		fixture.ctx, fixture.instance, fixture.success,
	))
	require.Equal(t, 1, fixture.notifications.mutationCalls)

	fixture.notifications.card.RenderPayloadJSON = replaceRoleCardValue(
		t,
		fixture.notifications.card.RenderPayloadJSON,
		"rlv_result",
		"rlv_wrong",
	)
	require.Error(t, fixture.observer.ProjectTerminal(
		fixture.ctx, fixture.instance, fixture.success,
	))
	require.Equal(t, 1, fixture.notifications.mutationCalls,
		"resolved content mismatch must be rejected rather than overwritten")
}

type roleManagementTerminalFixture struct {
	ctx            context.Context
	db             coredb.DB
	instance       approval.Instance
	assignment     approval.Assignment
	notifications  *agentManagementHandlerNotifications
	observer       *RoleManagementTerminalObserver
	success        execution.RequestResult
	notificationID string
	messageID      string
}

func newRoleManagementTerminalFixture(
	t *testing.T,
	suffix string,
) *roleManagementTerminalFixture {
	t.Helper()
	ctx := context.Background()
	db := startApprovalAppRollbackDB(t, ctx)
	now := time.Date(2026, 8, 5, 10, 0, 0, 0, time.UTC)
	workspaceID := "wsp_role_terminal_" + suffix
	seedApprovalAppWorkspace(t, db, workspaceID, now)
	instance, assignment := seedApprovedRoleManagementApproval(
		t, ctx, db, workspaceID, now,
	)
	payload, err := decodeRoleManagementPayload(instance)
	require.NoError(t, err)
	processing, err := roleManagementCardContent(
		instance, assignment.ID, "", payload, roleManagementCardStatusProcessing,
	)
	require.NoError(t, err)

	namespace, objectType, objectID := BusinessNamespace, BusinessTypeInstance, instance.ID
	version := instance.Version
	basisKind, basisRef := RecipientBasisAssignment, assignment.ID
	recipient := assignment.ApproverMemberID
	notificationID := "btn_role_terminal_" + suffix
	messageID := "msg_role_terminal_" + suffix
	notifications := &agentManagementHandlerNotifications{
		notification: app.AppNotificationRecord{
			ID: notificationID, WorkspaceID: workspaceID,
			TargetKind:        app.AppNotificationTargetHumanDM,
			RecipientMemberID: &recipient, RecipientBasisKind: &basisKind,
			RecipientBasisRef: &basisRef, MessageID: messageID,
			ContentKind:             app.AppNotificationContentInteractiveCard,
			ClientNotificationID:    roleManagementReviewClientNotificationID(instance.ID),
			BusinessObjectNamespace: &namespace, BusinessObjectType: &objectType,
			BusinessObjectID: &objectID, BusinessObjectVersion: &version,
			Status: app.AppNotificationStatusActive, Revision: 2,
		},
		card: app.AppNotificationCardRecord{
			MessageID: messageID, AppNotificationID: notificationID,
			State:             app.AppNotificationCardStateActive,
			RenderPayloadJSON: processing.CardJSON,
		},
	}
	return &roleManagementTerminalFixture{
		ctx: ctx, db: db, instance: instance, assignment: assignment,
		notifications: notifications,
		observer: NewRoleManagementTerminalObserver(
			approval.New(db), NewProjector(notifications, NewProvisioner(db)),
		),
		success: execution.RequestResult{
			ApprovalID: instance.ID, ApplicantAgentID: "agt_role_terminal",
			RequestID: "req_role_terminal", Action: execution.ActionCreate,
			Status: execution.RequestSucceeded,
			RoleID: "role_result", RoleVersionID: "rlv_result",
		},
		notificationID: notificationID,
		messageID:      messageID,
	}
}

func seedApprovedRoleManagementApproval(
	t *testing.T,
	ctx context.Context,
	db coredb.DB,
	workspaceID string,
	now time.Time,
) (approval.Instance, approval.Assignment) {
	t.Helper()
	payload, err := json.Marshal(execution.ApprovalPayload{
		SchemaVersion:    execution.PayloadSchemaVersion,
		RequestID:        "req_role_terminal",
		ApplicantAgentID: "agt_role_terminal",
		Action:           execution.ActionCreate,
		Create: &execution.RoleCreateProposal{
			Name: "Release reviewer", Description: "Own releases",
			Definition: "Review every release",
		},
	})
	require.NoError(t, err)
	var canonicalValue any
	decoder := json.NewDecoder(strings.NewReader(string(payload)))
	decoder.UseNumber()
	require.NoError(t, decoder.Decode(&canonicalValue))
	payload, err = json.Marshal(canonicalValue)
	require.NoError(t, err)
	digest := sha256.Sum256(payload)
	idempotencyKey, err := execution.RoleIdempotencyKey(
		workspaceID, "agt_role_terminal", "req_role_terminal",
	)
	require.NoError(t, err)
	instance, assignments, created, err := approval.New(db).Create(ctx, approval.CreateParams{
		ID: "appr_role_terminal_" + workspaceID, WorkspaceID: workspaceID,
		SubjectType: execution.SubjectType, SubjectRef: idempotencyKey,
		SubjectVersion: 1, Payload: payload,
		PayloadDigest:     fmt.Sprintf("%x", digest[:]),
		IdempotencyKey:    idempotencyKey,
		PendingKey:        "role-terminal:" + workspaceID,
		ApplicantMemberID: "mem_approval_bot_member",
		ApproverMemberIDs: []string{"mem_approval_bot_admin"},
		CorrelationID:     "raw-upstream-secret", Now: now,
	})
	require.NoError(t, err)
	require.True(t, created)
	require.Len(t, assignments, 1)
	mustExec(t, db, `
UPDATE approval_instances
SET state='approved', decision='approved',
    decided_by_member_id=$3, version=2, updated_at=$4, decided_at=$4
WHERE workspace_id=$1 AND id=$2
`, workspaceID, instance.ID, assignments[0].ApproverMemberID, now.Add(time.Second))
	mustExec(t, db, `
UPDATE approval_assignments
SET status='approved', version=2, updated_at=$3, closed_at=$3
WHERE workspace_id=$1 AND id=$2
`, workspaceID, assignments[0].ID, now.Add(time.Second))
	instance, err = approval.New(db).Load(ctx, workspaceID, instance.ID)
	require.NoError(t, err)
	return instance, assignments[0]
}

func replaceRoleCardValue(
	t *testing.T,
	value json.RawMessage,
	old string,
	replacement string,
) json.RawMessage {
	t.Helper()
	replaced := strings.Replace(string(value), old, replacement, 1)
	require.NotEqual(t, string(value), replaced)
	return json.RawMessage(replaced)
}

func roleCardContainsKey(value any, forbidden string) bool {
	switch value := value.(type) {
	case map[string]any:
		for key, child := range value {
			if strings.EqualFold(key, forbidden) || roleCardContainsKey(child, forbidden) {
				return true
			}
		}
	case []any:
		for _, child := range value {
			if roleCardContainsKey(child, forbidden) {
				return true
			}
		}
	}
	return false
}
