package organization

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

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

	runtime := startOrganizationServiceRuntime(t)
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")
	target := createVerifiedOrganizationUser(t, runtime, "target@example.com", "target")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Acme Org", "acme-org")
	require.NoError(t, err)

	account, err := runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", target.Email, "member")
	require.NoError(t, err)
	syncedTeam, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Engineering", "engineering", "")
	require.NoError(t, err)
	syncedUnit, err := runtime.service.CreateOrganizationUnitForUser(runtime.ctx, owner.ID, organization.ID, CreateOrganizationUnitParams{
		Name: "Engineering Unit",
		Slug: "engineering-unit",
	})
	require.NoError(t, err)

	require.NoError(t, setOrganizationDirectoryBackendForTests(runtime, organization.ID, organizationDirectoryBackendExternalShadow))

	syncedTeam, err = runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, syncedTeam.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-engineering",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationTeamSourceSynced, syncedTeam.Source)
	require.Equal(t, organizationSyncModeShadow, syncedTeam.SyncMode)
	require.Equal(t, "scim", syncedTeam.SyncSource)
	require.True(t, syncedTeam.ReadOnly)

	syncedUnit, err = runtime.service.MarkOrganizationUnitSynced(runtime.ctx, organization.ID, syncedUnit.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "unit-engineering",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationSyncModeShadow, syncedUnit.SyncMode)
	require.Equal(t, "scim", syncedUnit.SyncSource)
	require.True(t, syncedUnit.ReadOnly)

	syncedAccount, err := runtime.service.MarkOrganizationMemberSynced(runtime.ctx, organization.ID, account.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "user-target",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationMemberSourceSync, syncedAccount.Source)
	require.Equal(t, organizationSyncModeShadow, syncedAccount.SyncMode)
	require.Equal(t, "scim", syncedAccount.SyncSource)
	require.True(t, syncedAccount.ReadOnly)

	localShadowTeam, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Shadow Local", "shadow-local", "")
	require.NoError(t, err)
	localShadowUnit, err := runtime.service.CreateOrganizationUnitForUser(runtime.ctx, owner.ID, organization.ID, CreateOrganizationUnitParams{
		Name: "Shadow Local Unit",
		Slug: "shadow-local-unit",
	})
	require.NoError(t, err)

	_, err = runtime.service.AddOrganizationTeamMemberForUser(runtime.ctx, owner.ID, organization.ID, syncedTeam.ID, AddOrganizationTeamMemberParams{
		MemberID: account.ID,
	})
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "managed by provider sync")

	_, err = runtime.service.AddOrganizationUnitMemberForUser(runtime.ctx, owner.ID, organization.ID, syncedUnit.ID, AddOrganizationUnitMemberParams{
		MemberID: account.ID,
	})
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "managed by provider sync")

	_, err = runtime.service.AddOrganizationTeamMemberForUser(runtime.ctx, owner.ID, organization.ID, localShadowTeam.ID, AddOrganizationTeamMemberParams{
		MemberID: account.ID,
	})
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "member is managed by provider sync")

	_, err = runtime.service.AddOrganizationUnitMemberForUser(runtime.ctx, owner.ID, organization.ID, localShadowUnit.ID, AddOrganizationUnitMemberParams{
		MemberID: account.ID,
	})
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "member is managed by provider sync")
}

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

	runtime := startOrganizationServiceRuntime(t)
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")
	target := createVerifiedOrganizationUser(t, runtime, "target@example.com", "target")
	other := createVerifiedOrganizationUser(t, runtime, "other@example.com", "other")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Acme Org", "acme-org")
	require.NoError(t, err)

	account, err := runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", target.Email, "member")
	require.NoError(t, err)
	engineeringTeam, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Engineering", "engineering", "")
	require.NoError(t, err)
	_, err = runtime.service.AddOrganizationTeamMemberForUser(runtime.ctx, owner.ID, organization.ID, engineeringTeam.ID, AddOrganizationTeamMemberParams{
		MemberID: account.ID,
	})
	require.NoError(t, err)

	require.NoError(t, setOrganizationDirectoryBackendForTests(runtime, organization.ID, organizationDirectoryBackendExternalAuthority))

	engineeringTeam, err = runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, engineeringTeam.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-engineering",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationSyncModeAuthority, engineeringTeam.SyncMode)

	syncedAccount, err := runtime.service.MarkOrganizationMemberSynced(runtime.ctx, organization.ID, account.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "user-target",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationSyncModeAuthority, syncedAccount.SyncMode)

	_, err = runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Blocked Team", "blocked-team", "")
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "externally managed")

	_, err = runtime.service.CreateOrganizationUnitForUser(runtime.ctx, owner.ID, organization.ID, CreateOrganizationUnitParams{
		Name: "Blocked Unit",
		Slug: "blocked-unit",
	})
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "externally managed")

	_, err = runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", other.Email, "member")
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "externally managed")

	err = runtime.service.RemoveOrganizationTeamMemberForUser(runtime.ctx, owner.ID, organization.ID, engineeringTeam.ID, account.ID)
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
	require.Contains(t, err.Error(), "externally managed")

	workspace, err := runtime.service.CreateOrganizationWorkspaceForUser(runtime.ctx, owner.ID, organization.ID, "Project One", "project-one")
	require.NoError(t, err)

	grant, err := runtime.service.GrantOrganizationTeamWorkspaceAccessForUser(runtime.ctx, owner.ID, organization.ID, workspace.ID, engineeringTeam.ID, "member")
	require.NoError(t, err)
	require.Equal(t, "sync_rule", grant.Source)

	workspaces, err := runtime.store.Workspace.ListUserWorkspaces(runtime.ctx, target.ID)
	require.NoError(t, err)
	require.Contains(t, collectWorkspaceIDs(workspaces), workspace.ID)

	var directGrantCount int
	require.NoError(t, runtime.pool.QueryRow(runtime.ctx, `
SELECT COUNT(*)
FROM organization_workspace_member_grants
WHERE organization_id = $1
  AND workspace_id = $2
  AND organization_member_id = $3
  AND status = 'active'
`, organization.ID, workspace.ID, account.ID).Scan(&directGrantCount))
	require.Zero(t, directGrantCount)

	var appliedSource string
	require.NoError(t, runtime.pool.QueryRow(runtime.ctx, `
SELECT applied_source
FROM organization_workspace_member_projections
WHERE organization_id = $1
  AND workspace_id = $2
  AND organization_member_id = $3
`, organization.ID, workspace.ID, account.ID).Scan(&appliedSource))
	require.Equal(t, "sync_rule", appliedSource)
}

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

	runtime := startOrganizationServiceRuntime(t)
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")
	target := createVerifiedOrganizationUser(t, runtime, "target@example.com", "target")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Acme Org", "acme-org")
	require.NoError(t, err)

	account, err := runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", target.Email, "member")
	require.NoError(t, err)
	syncedTeam, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Engineering", "engineering", "")
	require.NoError(t, err)
	syncedUnit, err := runtime.service.CreateOrganizationUnitForUser(runtime.ctx, owner.ID, organization.ID, CreateOrganizationUnitParams{
		Name: "Engineering Unit",
		Slug: "engineering-unit",
	})
	require.NoError(t, err)

	_, err = runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendExternalShadow)
	require.NoError(t, err)

	syncedTeam, err = runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, syncedTeam.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-engineering",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationSyncModeShadow, syncedTeam.SyncMode)

	syncedUnit, err = runtime.service.MarkOrganizationUnitSynced(runtime.ctx, organization.ID, syncedUnit.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "unit-engineering",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationSyncModeShadow, syncedUnit.SyncMode)

	syncedAccount, err := runtime.service.MarkOrganizationMemberSynced(runtime.ctx, organization.ID, account.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "user-target",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.Equal(t, organizationSyncModeShadow, syncedAccount.SyncMode)
	require.True(t, syncedAccount.ReadOnly)

	updatedSettings, err := runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendEmbedded)
	require.NoError(t, err)
	require.Equal(t, organizationDirectoryBackendEmbedded, updatedSettings.DirectoryBackend)

	teamMembership, err := runtime.service.AddOrganizationTeamMemberForUser(runtime.ctx, owner.ID, organization.ID, syncedTeam.ID, AddOrganizationTeamMemberParams{
		MemberID: account.ID,
	})
	require.NoError(t, err)
	require.Equal(t, syncedTeam.ID, teamMembership.TeamID)
	require.Equal(t, account.ID, teamMembership.MemberID)

	unitMembership, err := runtime.service.AddOrganizationUnitMemberForUser(runtime.ctx, owner.ID, organization.ID, syncedUnit.ID, AddOrganizationUnitMemberParams{
		MemberID: account.ID,
	})
	require.NoError(t, err)
	require.Equal(t, syncedUnit.ID, unitMembership.UnitID)
	require.Equal(t, account.ID, unitMembership.MemberID)
}

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

	runtime := startOrganizationServiceRuntime(t)
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")
	target := createVerifiedOrganizationUser(t, runtime, "target@example.com", "target")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Acme Org", "acme-org")
	require.NoError(t, err)

	team, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Engineering", "engineering", "")
	require.NoError(t, err)
	unit, err := runtime.service.CreateOrganizationUnitForUser(runtime.ctx, owner.ID, organization.ID, CreateOrganizationUnitParams{
		Name: "Engineering Unit",
		Slug: "engineering-unit",
	})
	require.NoError(t, err)
	account, err := runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", target.Email, "member")
	require.NoError(t, err)

	settings, err := runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendExternalShadow)
	require.NoError(t, err)

	syncedTeam, err := runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, team.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-engineering",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.NotEmpty(t, syncedTeam.LastSyncRunID)
	require.EqualValues(t, settings.DirectoryRevision, syncedTeam.LastSyncDirectoryRevision)
	require.Equal(t, organizationDirectorySyncRunStatusCompleted, syncedTeam.LastSyncRunStatus)

	syncedUnit, err := runtime.service.MarkOrganizationUnitSynced(runtime.ctx, organization.ID, unit.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "unit-engineering",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.NotEmpty(t, syncedUnit.LastSyncRunID)
	require.EqualValues(t, settings.DirectoryRevision, syncedUnit.LastSyncDirectoryRevision)
	require.Equal(t, organizationDirectorySyncRunStatusCompleted, syncedUnit.LastSyncRunStatus)

	syncedAccount, err := runtime.service.MarkOrganizationMemberSynced(runtime.ctx, organization.ID, account.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "user-target",
		SyncSource:       "scim",
	})
	require.NoError(t, err)
	require.NotEmpty(t, syncedAccount.LastSyncRunID)
	require.EqualValues(t, settings.DirectoryRevision, syncedAccount.LastSyncDirectoryRevision)
	require.Equal(t, organizationDirectorySyncRunStatusCompleted, syncedAccount.LastSyncRunStatus)

	var syncRunCount int
	require.NoError(t, runtime.pool.QueryRow(runtime.ctx, `
SELECT COUNT(*)
FROM organization_directory_sync_runs
WHERE organization_id = $1
`, organization.ID).Scan(&syncRunCount))
	require.Equal(t, 3, syncRunCount)

	var memberSyncRunID string
	require.NoError(t, runtime.pool.QueryRow(runtime.ctx, `
SELECT COALESCE(last_sync_run_id, '')
FROM organization_members
WHERE organization_id = $1
  AND id = $2
`, organization.ID, account.ID).Scan(&memberSyncRunID))
	require.Equal(t, syncedAccount.LastSyncRunID, memberSyncRunID)
}

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

	runtime := startOrganizationServiceRuntime(t)
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")
	target := createVerifiedOrganizationUser(t, runtime, "target@example.com", "target")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Acme Org", "acme-org")
	require.NoError(t, err)

	team, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Engineering", "engineering", "")
	require.NoError(t, err)
	otherTeam, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Operations", "operations", "")
	require.NoError(t, err)
	unit, err := runtime.service.CreateOrganizationUnitForUser(runtime.ctx, owner.ID, organization.ID, CreateOrganizationUnitParams{
		Name: "Engineering Unit",
		Slug: "engineering-unit",
	})
	require.NoError(t, err)
	account, err := runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", target.Email, "member")
	require.NoError(t, err)

	settings, err := runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendExternalShadow)
	require.NoError(t, err)

	syncRunID, err := insertOrganizationDirectorySyncRunForTests(runtime, organization.ID, settings.DirectoryRevision, "okta", "scim", organizationDirectorySyncRunStatusRunning)
	require.NoError(t, err)

	syncedTeam, err := runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, team.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-engineering",
		SyncSource:       "scim",
		SyncRunID:        syncRunID,
	})
	require.NoError(t, err)
	require.Equal(t, syncRunID, syncedTeam.LastSyncRunID)
	require.EqualValues(t, settings.DirectoryRevision, syncedTeam.LastSyncDirectoryRevision)
	require.Equal(t, organizationDirectorySyncRunStatusRunning, syncedTeam.LastSyncRunStatus)

	syncedUnit, err := runtime.service.MarkOrganizationUnitSynced(runtime.ctx, organization.ID, unit.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "unit-engineering",
		SyncSource:       "scim",
		SyncRunID:        syncRunID,
	})
	require.NoError(t, err)
	require.Equal(t, syncRunID, syncedUnit.LastSyncRunID)

	syncedAccount, err := runtime.service.MarkOrganizationMemberSynced(runtime.ctx, organization.ID, account.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "user-target",
		SyncSource:       "scim",
		SyncRunID:        syncRunID,
	})
	require.NoError(t, err)
	require.Equal(t, syncRunID, syncedAccount.LastSyncRunID)

	settings, err = runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendExternalAuthority)
	require.NoError(t, err)

	_, err = runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, otherTeam.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-operations",
		SyncSource:       "scim",
		SyncRunID:        syncRunID,
	})
	require.ErrorIs(t, err, storeerr.ErrConflict)
	require.Contains(t, err.Error(), "directory backend revision changed")
}

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

	runtime := startOrganizationServiceRuntime(t)
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")
	target := createVerifiedOrganizationUser(t, runtime, "target@example.com", "target")
	stranger := createVerifiedOrganizationUser(t, runtime, "stranger@example.com", "stranger")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Acme Org", "acme-org")
	require.NoError(t, err)

	team, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Engineering", "engineering", "")
	require.NoError(t, err)
	otherTeam, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Operations", "operations", "")
	require.NoError(t, err)
	unit, err := runtime.service.CreateOrganizationUnitForUser(runtime.ctx, owner.ID, organization.ID, CreateOrganizationUnitParams{
		Name: "Engineering Unit",
		Slug: "engineering-unit",
	})
	require.NoError(t, err)
	account, err := runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", target.Email, "member")
	require.NoError(t, err)

	settings, err := runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendExternalShadow)
	require.NoError(t, err)

	syncRunID, err := insertOrganizationDirectorySyncRunForTests(runtime, organization.ID, settings.DirectoryRevision, "okta", "scim", organizationDirectorySyncRunStatusCompleted)
	require.NoError(t, err)

	_, err = runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, team.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-engineering",
		SyncSource:       "scim",
		SyncRunID:        syncRunID,
	})
	require.NoError(t, err)

	_, err = runtime.service.MarkOrganizationUnitSynced(runtime.ctx, organization.ID, unit.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "unit-engineering",
		SyncSource:       "scim",
		SyncRunID:        syncRunID,
	})
	require.NoError(t, err)

	_, err = runtime.service.MarkOrganizationMemberSynced(runtime.ctx, organization.ID, account.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "user-target",
		SyncSource:       "scim",
		SyncRunID:        syncRunID,
	})
	require.NoError(t, err)

	_, err = runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, otherTeam.ID, MarkOrganizationSyncedParams{
		ExternalProvider: "okta",
		ExternalID:       "team-operations",
		SyncSource:       "scim",
	})
	require.NoError(t, err)

	detail, err := runtime.service.GetOrganizationDirectorySyncRunForUser(runtime.ctx, owner.ID, organization.ID, syncRunID)
	require.NoError(t, err)
	require.Equal(t, syncRunID, detail.SyncRunID)
	require.Equal(t, "okta", detail.ExternalProvider)
	require.Equal(t, "scim", detail.SyncSource)
	require.Equal(t, organizationDirectorySyncRunStatusCompleted, detail.Status)
	require.EqualValues(t, settings.DirectoryRevision, detail.DirectoryRevision)
	require.NotNil(t, detail.StartedAt)
	require.NotNil(t, detail.CompletedAt)
	require.Equal(t, 1, detail.LinkedTeamCount)
	require.Equal(t, 1, detail.LinkedUnitCount)
	require.Equal(t, 1, detail.LinkedMemberCount)

	_, err = runtime.service.GetOrganizationDirectorySyncRunForUser(runtime.ctx, stranger.ID, organization.ID, syncRunID)
	require.ErrorIs(t, err, storeerr.ErrForbidden)
}

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

	runtime := startOrganizationServiceRuntime(t)
	owner := bootstrapOrganizationAdmin(t, runtime, "owner@example.com", "owner")
	target := createVerifiedOrganizationUser(t, runtime, "target@example.com", "target")

	organization, err := runtime.service.CreateOrganizationForUser(runtime.ctx, owner, "Acme Org", "acme-org")
	require.NoError(t, err)

	team, err := runtime.service.CreateOrganizationTeamForUser(runtime.ctx, owner.ID, organization.ID, "Engineering", "engineering", "")
	require.NoError(t, err)
	account, err := runtime.service.CreateOrganizationMemberForUser(runtime.ctx, owner.ID, organization.ID, "", target.Email, "member")
	require.NoError(t, err)

	settings, err := runtime.service.GetOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID)
	require.NoError(t, err)
	require.EqualValues(t, 1, settings.DirectoryRevision)

	settings, err = runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendExternalShadow)
	require.NoError(t, err)
	require.EqualValues(t, 2, settings.DirectoryRevision)
	staleRevision := settings.DirectoryRevision

	settings, err = runtime.service.UpdateOrganizationSettingsForUser(runtime.ctx, owner.ID, organization.ID, organizationDirectoryBackendExternalAuthority)
	require.NoError(t, err)
	require.EqualValues(t, 3, settings.DirectoryRevision)

	_, err = runtime.service.MarkOrganizationTeamSynced(runtime.ctx, organization.ID, team.ID, MarkOrganizationSyncedParams{
		ExternalProvider:          "okta",
		ExternalID:                "team-engineering",
		SyncSource:                "scim",
		ExpectedDirectoryRevision: &staleRevision,
	})
	require.ErrorIs(t, err, storeerr.ErrConflict)
	require.Contains(t, err.Error(), "directory backend revision changed")

	syncedAccount, err := runtime.service.MarkOrganizationMemberSynced(runtime.ctx, organization.ID, account.ID, MarkOrganizationSyncedParams{
		ExternalProvider:          "okta",
		ExternalID:                "user-target",
		SyncSource:                "scim",
		ExpectedDirectoryRevision: &settings.DirectoryRevision,
	})
	require.NoError(t, err)
	require.Equal(t, organizationSyncModeAuthority, syncedAccount.SyncMode)
}

func setOrganizationDirectoryBackendForTests(runtime serviceTestRuntime, organizationID string, backend string) error {
	_, err := runtime.pool.Exec(runtime.ctx, `
UPDATE organization_settings
SET directory_backend = $2,
    directory_revision = CASE
        WHEN directory_backend = $2 THEN directory_revision
        ELSE directory_revision + 1
    END,
    updated_at = NOW()
WHERE organization_id = $1
`, organizationID, backend)
	return err
}

func insertOrganizationDirectorySyncRunForTests(runtime serviceTestRuntime, organizationID string, directoryRevision int64, externalProvider string, syncSource string, status string) (string, error) {
	syncRunID, err := identifiers.NewEntityID("odsr")
	if err != nil {
		return "", err
	}

	now := time.Now().UTC()
	var completedAt any
	if status == organizationDirectorySyncRunStatusCompleted {
		completedAt = now
	}
	_, err = runtime.pool.Exec(runtime.ctx, `
INSERT INTO organization_directory_sync_runs (
    id,
    organization_id,
    directory_revision,
    external_provider,
    sync_source,
    status,
    started_at,
    completed_at,
    created_at,
    updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $7, $7)
`, syncRunID, organizationID, directoryRevision, externalProvider, syncSource, status, now, completedAt)
	if err != nil {
		return "", err
	}
	return syncRunID, nil
}

func collectWorkspaceIDs(items []workspace.Summary) []string {
	ids := make([]string, 0, len(items))
	for _, item := range items {
		ids = append(ids, item.WorkspaceID)
	}
	return ids
}
