package server

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/config"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/forwarder"
	"github.com/tidbcloud/AgentTeam/internal/service/role/execution"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	executionstore "github.com/tidbcloud/AgentTeam/internal/store/role/execution"
)

func TestRoleManagementPreflightAdapterMapsClosedActions(t *testing.T) {
	updateName := "Reviewer"
	updateDescription := ""
	updateDefinition := "Review changes carefully"

	tests := []struct {
		name        string
		payload     execution.ApprovalPayload
		wantRequest forwarder.RoleOperationRequest
		wantOwned   []string
	}{
		{
			name: "create",
			payload: roleManagementApprovalPayload(execution.ActionCreate,
				execution.RoleCreateProposal{
					Name: "Reviewer", Definition: "Review changes", Description: "Careful reviewer",
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Create: &forwarder.RoleOperationCreateRequest{
					Name: "Reviewer", Definition: "Review changes", Description: "Careful reviewer",
				},
			},
		},
		{
			name: "update",
			payload: roleManagementApprovalPayload(execution.ActionUpdate,
				execution.RoleUpdateProposal{
					RoleID: "role_1", ExpectedRoleVersionID: "rlv_1",
					Name: &updateName, Description: &updateDescription,
					Definition: &updateDefinition, Visibility: "workspace",
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Update: &forwarder.RoleOperationUpdateRequest{
					RoleID: "role_1", ExpectedRoleVersionID: "rlv_1",
					Name: &updateName, Description: &updateDescription,
					Definition: &updateDefinition, Visibility: roleManagementStringPointer("workspace"),
				},
			},
		},
		{
			name: "delete",
			payload: roleManagementApprovalPayload(execution.ActionDelete,
				execution.RoleDeleteProposal{
					RoleID: "role_2", ExpectedRoleVersionID: "rlv_2",
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Delete: &forwarder.RoleOperationDeleteRequest{
					RoleID: "role_2", ExpectedRoleVersionID: "rlv_2",
				},
			},
		},
		{
			name: "apply",
			payload: roleManagementApprovalPayload(execution.ActionApply,
				execution.RoleApplyProposal{
					RoleID: "role_3", RoleVersionID: "rlv_3", AgentID: "agt_target",
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Apply: &forwarder.RoleOperationApplyRequest{
					RoleID: "role_3", RoleVersionID: "rlv_3", AgentID: "agt_target",
				},
			},
			wantOwned: []string{"agt_target"},
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			var gotPrincipal forwarder.RoleOperationPrincipal
			var gotRequest forwarder.RoleOperationRequest
			gateway := &fakeRoleManagementOperationGateway{
				preflight: func(
					_ context.Context,
					principal forwarder.RoleOperationPrincipal,
					request forwarder.RoleOperationRequest,
				) error {
					gotPrincipal, gotRequest = principal, request
					return nil
				},
			}

			err := preflightRoleManagementOperation(
				context.Background(),
				gateway,
				execution.PreflightCommand{
					Principal: agent.AuthenticatedAgent{
						WorkspaceID: "wsp_1", AgentID: "agt_applicant",
					},
					Manager: execution.Manager{
						UserID: "usr_manager", MemberID: "mem_manager",
					},
					Payload: tt.payload,
				},
			)

			require.NoError(t, err)
			require.Equal(t, forwarder.RoleOperationPrincipal{
				WorkspaceID:   "wsp_1",
				Subject:       "usr_manager",
				OwnedAgentIDs: tt.wantOwned,
			}, gotPrincipal)
			require.Equal(t, tt.wantRequest, gotRequest)
		})
	}
}

func TestRoleManagementPreflightAdapterMapsOnlyStableGatewayCodes(t *testing.T) {
	tests := []struct {
		code    string
		outcome execution.ApplyOutcome
	}{
		{code: "invalid_request", outcome: execution.ApplyInvalidRequest},
		{code: "authority_denied", outcome: execution.ApplyAuthorityDenied},
		{code: "not_found", outcome: execution.ApplyNotFound},
		{code: "role_version_conflict", outcome: execution.ApplyVersionConflict},
		{code: "role_operation_conflict", outcome: execution.ApplyOperationConflict},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.code, func(t *testing.T) {
			gateway := &fakeRoleManagementOperationGateway{
				preflight: func(
					context.Context,
					forwarder.RoleOperationPrincipal,
					forwarder.RoleOperationRequest,
				) error {
					return fmt.Errorf("gateway rejected: %w", &forwarder.DeterministicRoleOperationError{
						Code: tt.code, Message: "rejected",
					})
				},
			}

			err := preflightRoleManagementOperation(
				context.Background(), gateway, validRoleManagementPreflightCommand(),
			)

			var applyErr *execution.ApplyError
			require.ErrorAs(t, err, &applyErr)
			require.Equal(t, tt.outcome, applyErr.Outcome)
		})
	}
}

func TestRoleManagementPreflightAdapterKeepsUncertainErrorsUnclassified(t *testing.T) {
	unknownCode := &forwarder.DeterministicRoleOperationError{
		Code: "future_code", Message: "future failure",
	}
	networkErr := errors.New("network down")

	for name, gatewayErr := range map[string]error{
		"unknown deterministic code": unknownCode,
		"network":                    networkErr,
	} {
		name, gatewayErr := name, gatewayErr
		t.Run(name, func(t *testing.T) {
			gateway := &fakeRoleManagementOperationGateway{
				preflight: func(
					context.Context,
					forwarder.RoleOperationPrincipal,
					forwarder.RoleOperationRequest,
				) error {
					return gatewayErr
				},
			}

			err := preflightRoleManagementOperation(
				context.Background(), gateway, validRoleManagementPreflightCommand(),
			)

			require.ErrorIs(t, err, gatewayErr)
			var applyErr *execution.ApplyError
			require.False(t, errors.As(err, &applyErr))
		})
	}
}

func TestRoleManagementApplyAdapterMapsClosedActionsAndResult(t *testing.T) {
	updateDescription := ""
	tests := []struct {
		name        string
		payload     execution.ApprovalPayload
		wantRequest forwarder.RoleOperationRequest
	}{
		{
			name: "create",
			payload: roleManagementApprovalPayload(execution.ActionCreate,
				execution.RoleCreateProposal{
					Name: "Reviewer", Definition: "Review changes", Description: "Careful reviewer",
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Create: &forwarder.RoleOperationCreateRequest{
					Name: "Reviewer", Definition: "Review changes", Description: "Careful reviewer",
				},
			},
		},
		{
			name: "update",
			payload: roleManagementApprovalPayload(execution.ActionUpdate,
				execution.RoleUpdateProposal{
					RoleID: "role_1", ExpectedRoleVersionID: "rlv_1",
					Description: &updateDescription,
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Update: &forwarder.RoleOperationUpdateRequest{
					RoleID: "role_1", ExpectedRoleVersionID: "rlv_1",
					Description: &updateDescription,
				},
			},
		},
		{
			name: "delete",
			payload: roleManagementApprovalPayload(execution.ActionDelete,
				execution.RoleDeleteProposal{
					RoleID: "role_2", ExpectedRoleVersionID: "rlv_2",
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Delete: &forwarder.RoleOperationDeleteRequest{
					RoleID: "role_2", ExpectedRoleVersionID: "rlv_2",
				},
			},
		},
		{
			name: "apply",
			payload: roleManagementApprovalPayload(execution.ActionApply,
				execution.RoleApplyProposal{
					RoleID: "role_3", RoleVersionID: "rlv_3", AgentID: "agt_target",
				},
			),
			wantRequest: forwarder.RoleOperationRequest{
				Apply: &forwarder.RoleOperationApplyRequest{
					RoleID: "role_3", RoleVersionID: "rlv_3", AgentID: "agt_target",
				},
			},
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			gateway := &fakeRoleManagementOperationGateway{
				execute: func(
					_ context.Context,
					principal forwarder.RoleOperationPrincipal,
					request forwarder.RoleOperationExecuteRequest,
				) (forwarder.RoleOperationResult, error) {
					require.Equal(t, forwarder.RoleOperationPrincipal{
						WorkspaceID:   "wsp_1",
						Subject:       "usr_manager",
						OwnedAgentIDs: []string{"agt_target"},
					}, principal)
					require.Equal(t, "appr_1", request.OperationKey)
					require.Equal(t, "digest_1", request.RequestDigest)
					require.Equal(t, tt.wantRequest, request.RoleOperationRequest)
					return forwarder.RoleOperationResult{
						RoleID: "role_result", RoleVersionID: "rlv_result",
					}, nil
				},
			}

			got, err := applyRoleManagementOperation(
				context.Background(),
				gateway,
				execution.ApplyCommand{
					Approval: approval.Instance{
						ID: "appr_1", WorkspaceID: "wsp_1",
					},
					Payload:       tt.payload,
					OperationKey:  "appr_1",
					RequestDigest: "digest_1",
					Authority: execution.ExecutionAuthority{
						ManagerUserID: "usr_manager",
						OwnedAgentIDs: []string{"agt_target"},
					},
				},
			)

			require.NoError(t, err)
			require.Equal(t, executionstore.Success{
				RoleID: "role_result", RoleVersionID: "rlv_result",
			}, got)
		})
	}
}

func TestRoleManagementApplyAdapterMapsOnlyStableGatewayCodes(t *testing.T) {
	tests := []struct {
		code    string
		outcome execution.ApplyOutcome
	}{
		{code: "invalid_request", outcome: execution.ApplyInvalidRequest},
		{code: "authority_denied", outcome: execution.ApplyAuthorityDenied},
		{code: "not_found", outcome: execution.ApplyNotFound},
		{code: "role_version_conflict", outcome: execution.ApplyVersionConflict},
		{code: "role_operation_conflict", outcome: execution.ApplyOperationConflict},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.code, func(t *testing.T) {
			gateway := &fakeRoleManagementOperationGateway{
				execute: func(
					context.Context,
					forwarder.RoleOperationPrincipal,
					forwarder.RoleOperationExecuteRequest,
				) (forwarder.RoleOperationResult, error) {
					return forwarder.RoleOperationResult{},
						fmt.Errorf("gateway rejected: %w", &forwarder.DeterministicRoleOperationError{
							Code: tt.code, Message: "rejected",
						})
				},
			}

			_, err := applyRoleManagementOperation(
				context.Background(), gateway, validRoleManagementApplyCommand(),
			)

			var applyErr *execution.ApplyError
			require.ErrorAs(t, err, &applyErr)
			require.Equal(t, tt.outcome, applyErr.Outcome)
		})
	}
}

func TestRoleManagementApplyAdapterKeepsUncertainErrorsUnclassified(t *testing.T) {
	unknownCode := &forwarder.DeterministicRoleOperationError{
		Code: "future_code", Message: "future failure",
	}
	responseLost := &execution.ApplyError{
		Outcome: execution.ApplyResponseLost,
	}
	networkErr := errors.New("network down")

	for name, gatewayErr := range map[string]error{
		"unknown deterministic code": unknownCode,
		"response lost":              responseLost,
		"network":                    networkErr,
	} {
		name, gatewayErr := name, gatewayErr
		t.Run(name, func(t *testing.T) {
			gateway := &fakeRoleManagementOperationGateway{
				execute: func(
					context.Context,
					forwarder.RoleOperationPrincipal,
					forwarder.RoleOperationExecuteRequest,
				) (forwarder.RoleOperationResult, error) {
					return forwarder.RoleOperationResult{}, gatewayErr
				},
			}

			_, err := applyRoleManagementOperation(
				context.Background(), gateway, validRoleManagementApplyCommand(),
			)

			require.ErrorIs(t, err, gatewayErr)
			if gatewayErr != responseLost {
				var applyErr *execution.ApplyError
				require.False(t, errors.As(err, &applyErr))
			}
		})
	}
}

func TestRoleManagementRecoveryAdapterUsesImmutableApprovalAndMapsResult(t *testing.T) {
	payload := roleManagementApprovalPayload(
		execution.ActionApply,
		execution.RoleApplyProposal{
			RoleID: "role_1", RoleVersionID: "rlv_1", AgentID: "agt_target",
		},
	)
	gateway := &fakeRoleManagementOperationGateway{
		recover: func(
			_ context.Context,
			principal forwarder.RoleOperationPrincipal,
			request forwarder.RoleOperationExecuteRequest,
		) (forwarder.RoleOperationRecoveryResult, error) {
			require.Equal(t, forwarder.RoleOperationPrincipal{
				WorkspaceID: "wsp_1", Subject: "mem_decider",
			}, principal)
			require.Equal(t, "appr_1", request.OperationKey)
			require.Equal(t, "digest_1", request.RequestDigest)
			require.Equal(t, &forwarder.RoleOperationApplyRequest{
				RoleID: "role_1", RoleVersionID: "rlv_1", AgentID: "agt_target",
			}, request.Apply)
			return forwarder.RoleOperationRecoveryResult{
				Found: true, RoleID: "role_stable", RoleVersionID: "rlv_stable",
			}, nil
		},
	}

	result, found, err := recoverRoleManagementOperation(
		context.Background(),
		gateway,
		execution.RecoverAppliedCommand{
			Approval: approval.Instance{
				ID: "appr_1", WorkspaceID: "wsp_1",
				DecidedByMemberID: "mem_decider",
			},
			Payload: payload, OperationKey: "appr_1", RequestDigest: "digest_1",
		},
	)

	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, executionstore.Success{
		RoleID: "role_stable", RoleVersionID: "rlv_stable",
	}, result)
}

func TestRoleManagementRecoveryAdapterMapsOnlyReceiptConflict(t *testing.T) {
	gateway := &fakeRoleManagementOperationGateway{
		recover: func(
			context.Context,
			forwarder.RoleOperationPrincipal,
			forwarder.RoleOperationExecuteRequest,
		) (forwarder.RoleOperationRecoveryResult, error) {
			return forwarder.RoleOperationRecoveryResult{}, &forwarder.DeterministicRoleOperationError{
				Code: "role_operation_conflict", Message: "receipt mismatch",
			}
		},
	}

	_, _, err := recoverRoleManagementOperation(
		context.Background(),
		gateway,
		execution.RecoverAppliedCommand{
			Approval: approval.Instance{
				ID: "appr_1", WorkspaceID: "wsp_1",
				DecidedByMemberID: "mem_decider",
			},
			Payload: roleManagementApprovalPayload(
				execution.ActionDelete,
				execution.RoleDeleteProposal{
					RoleID: "role_1", ExpectedRoleVersionID: "rlv_1",
				},
			),
			OperationKey: "appr_1", RequestDigest: "digest_1",
		},
	)

	var applyErr *execution.ApplyError
	require.ErrorAs(t, err, &applyErr)
	require.Equal(t, execution.ApplyOperationConflict, applyErr.Outcome)
}

func TestRoleManagementRuntimeForConfigHonorsEnablementAndProfile(t *testing.T) {
	db := &fakeRoleManagementDB{}
	dependencies := roleManagementRuntimeOptions{
		DB:              db,
		Approvals:       approval.New(db),
		WorkspaceLister: &fakeRoleManagementWorkspaceLister{},
		Gateway:         &fakeRoleManagementOperationGateway{},
		LoadProjectedTerminal: func(
			context.Context,
			approval.Instance,
			execution.ApprovalPayload,
		) (execution.RequestResult, bool, error) {
			return execution.RequestResult{}, false, nil
		},
		ProjectTerminal: func(
			context.Context,
			approval.Instance,
			execution.RequestResult,
		) error {
			return nil
		},
	}

	for _, tt := range []struct {
		name    string
		config  config.Config
		enabled bool
	}{
		{
			name:   "disabled",
			config: config.Config{},
		},
		{
			name: "central",
			config: config.Config{
				Server: config.ServerConfig{Profile: config.ServerProfileCentral},
				MeshX:  config.MeshXConfig{Enabled: true},
			},
		},
		{
			name: "all without external base URL",
			config: config.Config{
				Server: config.ServerConfig{Profile: config.ServerProfileAll},
				MeshX:  config.MeshXConfig{Enabled: true},
			},
			enabled: true,
		},
		{
			name: "workspace without external base URL",
			config: config.Config{
				Server: config.ServerConfig{
					Profile: config.ServerProfileWorkspace, WorkspaceGroupID: "group_1",
				},
				MeshX: config.MeshXConfig{Enabled: true},
			},
			enabled: true,
		},
	} {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			options := roleManagementRuntimeOptions{Config: tt.config}
			if tt.enabled {
				options = dependencies
				options.Config = tt.config
			}

			runtime, err := newRoleManagementRuntimeForConfig(options)

			require.NoError(t, err)
			if !tt.enabled {
				require.Nil(t, runtime)
				return
			}
			require.NotNil(t, runtime)
			require.NotNil(t, runtime.service)
			require.NotNil(t, runtime.worker)
		})
	}
}

func TestRoleManagementRuntimeExposesItsExistingCoreStoreAsManager(t *testing.T) {
	t.Parallel()

	source, err := os.ReadFile("role_management.go")
	require.NoError(t, err)
	role := string(source)
	normalized := strings.Join(strings.Fields(role), " ")

	require.Equal(t, 1, strings.Count(role,
		"coreStore := execution.CoreSubmissionStore{"))
	require.Equal(t, 1, strings.Count(role,
		"ResolveManager:        coreStore.ResolveManager,"))
	require.Equal(t, 1, strings.Count(normalized,
		"return &roleManagementRuntime{ service: service, worker: worker, manager: coreStore, }, nil"))
	require.Equal(t, 1, strings.Count(normalized,
		"manager execution.CoreSubmissionStore"))
}

func TestRoleManagementWorkspaceSourceRefreshesOwnedWorkspaces(t *testing.T) {
	t.Run("all", func(t *testing.T) {
		db := &fakeRoleManagementDB{
			workspaceBatches: [][]string{
				{"wsp_1"},
				{"wsp_1", "wsp_2"},
			},
		}
		source, err := roleManagementWorkspaceIDsForConfig(config.Config{
			Server: config.ServerConfig{Profile: config.ServerProfileAll},
			MeshX:  config.MeshXConfig{Enabled: true},
		}, db, nil)
		require.NoError(t, err)
		require.NotNil(t, source)

		first, err := source(context.Background())
		require.NoError(t, err)
		second, err := source(context.Background())
		require.NoError(t, err)

		require.Equal(t, []string{"wsp_1"}, first)
		require.Equal(t, []string{"wsp_1", "wsp_2"}, second)
		require.Equal(t, 2, db.queryCalls)
	})

	t.Run("workspace", func(t *testing.T) {
		lister := &fakeRoleManagementWorkspaceLister{
			workspaceBatches: [][]string{
				{"wsp_2"},
				{"wsp_2", "wsp_3"},
			},
		}
		source, err := roleManagementWorkspaceIDsForConfig(config.Config{
			Server: config.ServerConfig{
				Profile: config.ServerProfileWorkspace, WorkspaceGroupID: " group_1 ",
			},
			MeshX: config.MeshXConfig{Enabled: true},
		}, nil, lister)
		require.NoError(t, err)
		require.NotNil(t, source)

		first, err := source(context.Background())
		require.NoError(t, err)
		second, err := source(context.Background())
		require.NoError(t, err)

		require.Equal(t, []string{"wsp_2"}, first)
		require.Equal(t, []string{"wsp_2", "wsp_3"}, second)
		require.Equal(t, []string{"group_1", "group_1"}, lister.groupIDs)
	})
}

func roleManagementApprovalPayload(
	action execution.RoleRequestAction,
	proposal execution.RoleProposal,
) execution.ApprovalPayload {
	payload := execution.ApprovalPayload{
		SchemaVersion:    execution.PayloadSchemaVersion,
		RequestID:        "request_1",
		ApplicantAgentID: "agt_applicant",
		Action:           action,
	}
	switch proposal := proposal.(type) {
	case execution.RoleCreateProposal:
		payload.Create = &proposal
	case execution.RoleUpdateProposal:
		payload.Update = &proposal
	case execution.RoleDeleteProposal:
		payload.Delete = &proposal
	case execution.RoleApplyProposal:
		payload.Apply = &proposal
	default:
		panic("unsupported Role proposal")
	}
	return payload
}

func validRoleManagementApplyCommand() execution.ApplyCommand {
	return execution.ApplyCommand{
		Approval: approval.Instance{ID: "appr_1", WorkspaceID: "wsp_1"},
		Payload: roleManagementApprovalPayload(
			execution.ActionDelete,
			execution.RoleDeleteProposal{
				RoleID: "role_1", ExpectedRoleVersionID: "rlv_1",
			},
		),
		OperationKey:  "appr_1",
		RequestDigest: "digest_1",
		Authority: execution.ExecutionAuthority{
			ManagerUserID: "usr_manager",
		},
	}
}

func validRoleManagementPreflightCommand() execution.PreflightCommand {
	return execution.PreflightCommand{
		Principal: agent.AuthenticatedAgent{WorkspaceID: "wsp_1"},
		Manager:   execution.Manager{UserID: "usr_manager"},
		Payload: roleManagementApprovalPayload(
			execution.ActionDelete,
			execution.RoleDeleteProposal{
				RoleID: "role_1", ExpectedRoleVersionID: "rlv_1",
			},
		),
	}
}

func roleManagementStringPointer(value string) *string {
	return &value
}

type fakeRoleManagementOperationGateway struct {
	preflight func(
		context.Context,
		forwarder.RoleOperationPrincipal,
		forwarder.RoleOperationRequest,
	) error
	execute func(
		context.Context,
		forwarder.RoleOperationPrincipal,
		forwarder.RoleOperationExecuteRequest,
	) (forwarder.RoleOperationResult, error)
	recover func(
		context.Context,
		forwarder.RoleOperationPrincipal,
		forwarder.RoleOperationExecuteRequest,
	) (forwarder.RoleOperationRecoveryResult, error)
}

func (gateway *fakeRoleManagementOperationGateway) Preflight(
	ctx context.Context,
	principal forwarder.RoleOperationPrincipal,
	request forwarder.RoleOperationRequest,
) error {
	if gateway.preflight == nil {
		return errors.New("unexpected Role preflight")
	}
	return gateway.preflight(ctx, principal, request)
}

func (gateway *fakeRoleManagementOperationGateway) Execute(
	ctx context.Context,
	principal forwarder.RoleOperationPrincipal,
	request forwarder.RoleOperationExecuteRequest,
) (forwarder.RoleOperationResult, error) {
	if gateway.execute == nil {
		return forwarder.RoleOperationResult{}, errors.New("unexpected Role execute")
	}
	return gateway.execute(ctx, principal, request)
}

func (gateway *fakeRoleManagementOperationGateway) Recover(
	ctx context.Context,
	principal forwarder.RoleOperationPrincipal,
	request forwarder.RoleOperationExecuteRequest,
) (forwarder.RoleOperationRecoveryResult, error) {
	if gateway.recover == nil {
		return forwarder.RoleOperationRecoveryResult{}, nil
	}
	return gateway.recover(ctx, principal, request)
}

type fakeRoleManagementWorkspaceLister struct {
	workspaceBatches [][]string
	groupIDs         []string
}

func (lister *fakeRoleManagementWorkspaceLister) ListRoutableWorkspaceIDsForGroup(
	_ context.Context,
	groupID string,
) ([]string, error) {
	lister.groupIDs = append(lister.groupIDs, groupID)
	if len(lister.workspaceBatches) == 0 {
		return nil, nil
	}
	index := len(lister.groupIDs) - 1
	if index >= len(lister.workspaceBatches) {
		index = len(lister.workspaceBatches) - 1
	}
	return append([]string(nil), lister.workspaceBatches[index]...), nil
}

type fakeRoleManagementDB struct {
	workspaceBatches [][]string
	queryCalls       int
}

func (db *fakeRoleManagementDB) Query(
	context.Context,
	string,
	...any,
) (coredb.Rows, error) {
	db.queryCalls++
	if len(db.workspaceBatches) == 0 {
		return &fakeRoleManagementRows{}, nil
	}
	index := db.queryCalls - 1
	if index >= len(db.workspaceBatches) {
		index = len(db.workspaceBatches) - 1
	}
	return &fakeRoleManagementRows{
		values: append([]string(nil), db.workspaceBatches[index]...),
		index:  -1,
	}, nil
}

func (*fakeRoleManagementDB) QueryRow(
	context.Context,
	string,
	...any,
) coredb.Row {
	return roleManagementErrorRow{err: errors.New("unexpected query row")}
}

func (*fakeRoleManagementDB) Exec(
	context.Context,
	string,
	...any,
) (coredb.Result, error) {
	return nil, errors.New("unexpected exec")
}

func (*fakeRoleManagementDB) BeginTx(
	context.Context,
	coredb.TxOptions,
) (coredb.Tx, error) {
	return nil, errors.New("unexpected transaction")
}

type fakeRoleManagementRows struct {
	values []string
	index  int
}

func (*fakeRoleManagementRows) Close() {}

func (*fakeRoleManagementRows) Err() error { return nil }

func (rows *fakeRoleManagementRows) Next() bool {
	rows.index++
	return rows.index < len(rows.values)
}

func (rows *fakeRoleManagementRows) Scan(destinations ...any) error {
	if rows.index < 0 || rows.index >= len(rows.values) ||
		len(destinations) != 1 {
		return errors.New("unexpected scan")
	}
	value, ok := destinations[0].(*string)
	if !ok {
		return errors.New("unexpected scan destination")
	}
	*value = rows.values[rows.index]
	return nil
}

type roleManagementErrorRow struct {
	err error
}

func (row roleManagementErrorRow) Scan(...any) error {
	return row.err
}
