package message

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

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

	require.Contains(t, taskHistoricalParentAuthoritySQL, "m.created_at >= COALESCE(sm.authority_from_at, sm.created_at)")
	require.Contains(t, taskHistoricalParentAuthoritySQL, "COALESCE(sm.authority_to_at, sm.removed_at)")
	require.NotContains(t, scopeReadableMembershipCoversMessageSQL, "message_scope.type = 'task'\\n      OR")
	require.NotContains(t, scopePrivateAuthorityGrantJoinsSQL, "CURRENT_TIMESTAMP")
}

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	repos := NewScopeRepositories(db)
	now := scopePrivateAuthorityNowForTest()

	workspaceID := "wsp_scope_private_parent"
	channelID := "scp_scope_private_parent_channel"
	threadID := "scp_scope_private_parent_thread"
	ownerMemberID := "mem_scope_private_parent_owner"
	granteeMemberID := "mem_scope_private_parent_grantee"
	collaboratorMemberID := "mem_scope_private_parent_collab"
	strangerMemberID := "mem_scope_private_parent_stranger"
	privateAgentID := "mem_scope_private_parent_agent"
	siblingAgentMemberID := "mem_scope_private_parent_sibling"
	crossOwnerID := "mem_scope_private_parent_cross_owner"
	crossOwnerAgentMemberID := "mem_scope_private_parent_cross_agent"

	insertMessagingWorkspace(t, ctx, db, workspaceID, now)
	insertMessagingUser(t, ctx, db, "usr_scope_private_parent_owner", "scope-private-parent-owner@example.com", now)
	insertMessagingHumanMember(t, ctx, db, ownerMemberID, workspaceID, "usr_scope_private_parent_owner", now)
	insertMessagingUser(t, ctx, db, "usr_scope_private_parent_grantee", "scope-private-parent-grantee@example.com", now)
	insertMessagingHumanMember(t, ctx, db, granteeMemberID, workspaceID, "usr_scope_private_parent_grantee", now)
	insertMessagingUser(t, ctx, db, "usr_scope_private_parent_collab", "scope-private-parent-collab@example.com", now)
	insertMessagingHumanMember(t, ctx, db, collaboratorMemberID, workspaceID, "usr_scope_private_parent_collab", now)
	insertMessagingUser(t, ctx, db, "usr_scope_private_parent_stranger", "scope-private-parent-stranger@example.com", now)
	insertMessagingHumanMember(t, ctx, db, strangerMemberID, workspaceID, "usr_scope_private_parent_stranger", now)
	insertMessagingAgentMember(t, ctx, db, privateAgentID, workspaceID, now)
	insertMessagingAgentMember(t, ctx, db, siblingAgentMemberID, workspaceID, now)
	insertMessagingUser(t, ctx, db, "usr_scope_private_parent_cross_owner", "scope-private-parent-cross-owner@example.com", now)
	insertMessagingHumanMember(t, ctx, db, crossOwnerID, workspaceID, "usr_scope_private_parent_cross_owner", now)
	insertMessagingAgentMember(t, ctx, db, crossOwnerAgentMemberID, workspaceID, now)
	insertMessagingMachine(t, ctx, db, "mach_scope_private_parent_agent", workspaceID, "usr_scope_private_parent_owner", now)
	insertMessagingAgent(t, ctx, db, "agt_scope_private_parent_agent", workspaceID, "mach_scope_private_parent_agent", privateAgentID, "scope-private-parent-agent", now)
	insertMessagingAgent(t, ctx, db, "agt_scope_private_parent_sibling", workspaceID, "mach_scope_private_parent_agent", siblingAgentMemberID, "scope-private-parent-sibling", now)
	mustExecMessaging(t, ctx, db, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, $1, $4, $5, $5)
`, "mach_scope_private_parent_cross_agent", workspaceID, "usr_scope_private_parent_cross_owner", "machine-key-cross-scope-private-parent", now)
	insertMessagingAgent(t, ctx, db, "agt_scope_private_parent_cross_agent", workspaceID, "mach_scope_private_parent_cross_agent", crossOwnerAgentMemberID, "scope-private-parent-cross-agent", now)

	insertMessagingChannel(t, ctx, db, channelID, workspaceID, "private-parent-channel", ownerMemberID, "", now)
	require.NoError(t, repos.Messages.Insert(ctx, scopePrivateAuthorityMessageForTest(workspaceID, channelID, "msg_scope_private_parent_root", 1, 1, ownerMemberID, ScopeActorHuman, now.Add(time.Second))))
	insertMessagingReplyThread(t, ctx, db, threadID, workspaceID, channelID, "msg_scope_private_parent_root", "scope-private-parent-root", "", now)
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: channelID, MemberID: privateAgentID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: channelID, MemberID: siblingAgentMemberID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: threadID, MemberID: privateAgentID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: threadID, MemberID: siblingAgentMemberID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: threadID, MemberID: crossOwnerAgentMemberID, Visibility: "private", OwnerMemberID: crossOwnerID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.PrivateVisibility.InsertGrant(ctx, ScopePrivateGrantRecord{ID: "grant_scope_private_parent_channel", WorkspaceID: workspaceID, ScopeID: channelID, OwnerMemberID: ownerMemberID, GranteeMemberID: granteeMemberID, GrantedByMemberID: ownerMemberID, CreatedAt: now}))
	insertScopeTaskCollaboratorGrantForMessageTest(t, ctx, db, "collab_scope_private_parent_channel", workspaceID, ownerMemberID, "channel", channelID, collaboratorMemberID, now)

	threadMessages := []ScopeMessageRecord{
		scopePrivateAuthorityMessageForTest(workspaceID, threadID, "msg_scope_private_parent_owner", 1, 10, ownerMemberID, ScopeActorHuman, now.Add(2*time.Second)),
		scopePrivateAuthorityMessageForTest(workspaceID, threadID, "msg_scope_private_parent_grantee", 2, 20, granteeMemberID, ScopeActorHuman, now.Add(3*time.Second)),
		scopePrivateAuthorityMessageForTest(workspaceID, threadID, "msg_scope_private_parent_collab", 3, 30, collaboratorMemberID, ScopeActorHuman, now.Add(4*time.Second)),
		scopePrivateAuthorityMessageForTest(workspaceID, threadID, "msg_scope_private_parent_stranger", 4, 40, strangerMemberID, ScopeActorHuman, now.Add(5*time.Second)),
		scopePrivateAuthorityMessageForTest(workspaceID, threadID, "msg_scope_private_parent_sibling", 5, 50, siblingAgentMemberID, ScopeActorPrivateAgent, now.Add(6*time.Second)),
		scopePrivateAuthorityMessageForTest(workspaceID, threadID, "msg_scope_private_parent_cross_agent", 6, 60, crossOwnerAgentMemberID, ScopeActorPrivateAgent, now.Add(7*time.Second)),
	}
	for _, message := range threadMessages {
		require.NoError(t, repos.Messages.Insert(ctx, message))
		require.NoError(t, repos.Search.InsertDoc(ctx, ScopeSearchDocRecord{
			MessageID:   message.ID,
			WorkspaceID: message.WorkspaceID,
			ScopeID:     message.ScopeID,
			GlobalSeq:   message.WorkspaceGlobalSeq,
			Content:     "parent " + message.ID,
			CreatedAt:   message.CreatedAt,
		}))
	}

	historyCandidates, err := repos.Messages.ListVisibilityCandidatesBeforeSeq(ctx, threadID, privateAgentID, 1<<63-1, 10)
	require.NoError(t, err)
	require.Equal(t, []string{
		"msg_scope_private_parent_sibling",
		"msg_scope_private_parent_collab",
		"msg_scope_private_parent_grantee",
		"msg_scope_private_parent_owner",
	}, visibilityCandidateMessageIDs(historyCandidates))

	strangerCandidates, err := repos.Messages.LoadVisibilityCandidatesByMessageID(ctx, workspaceID, privateAgentID, "msg_scope_private_parent_stranger")
	require.NoError(t, err)
	require.Empty(t, strangerCandidates)

	siblingCandidates, err := repos.Messages.LoadVisibilityCandidatesByMessageID(ctx, workspaceID, privateAgentID, "msg_scope_private_parent_sibling")
	require.NoError(t, err)
	require.Len(t, siblingCandidates, 1)
	require.NotEmpty(t, siblingCandidates[0].GrantID)

	api := NewScopeAPIService(New(db, nil, 0, Options{}))
	history, err := api.ListHistory(ctx, ScopeAPIHistoryRequest{
		WorkspaceID:    workspaceID,
		ScopeID:        threadID,
		ViewerMemberID: privateAgentID,
		ViewerType:     ScopeActorPrivateAgent,
		RequestKind:    ScopeVisibilityRequestHistory,
		Limit:          10,
	})
	require.NoError(t, err)
	require.Len(t, history.Messages, 4)
	require.Equal(t, "msg_scope_private_parent_sibling", history.Messages[0].ID)
	require.Equal(t, "msg_scope_private_parent_collab", history.Messages[1].ID)
	require.Equal(t, "msg_scope_private_parent_grantee", history.Messages[2].ID)
	require.Equal(t, "msg_scope_private_parent_owner", history.Messages[3].ID)

	require.Contains(t, scopeSearchVisibilityCandidatesQuery.SQL, "sibling_private")
	search, err := searchVisibleWithLoader(ctx, NewVisibilityFilter(), ScopeAPISearchRequest{
		WorkspaceID:    workspaceID,
		ScopeID:        threadID,
		ViewerMemberID: privateAgentID,
		ViewerType:     ScopeActorPrivateAgent,
		Query:          "parent",
		Limit:          10,
	}, 10, func(context.Context, MessageSearchCursor, int) ([]ScopeMessageVisibilityCandidate, error) {
		membership := ScopeMembershipRecord{ScopeID: threadID, MemberID: privateAgentID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1}
		candidates := make([]ScopeMessageVisibilityCandidate, 0, len(threadMessages))
		for _, record := range threadMessages {
			candidate := scopeSearchCandidate(record, membership)
			switch record.ID {
			case "msg_scope_private_parent_grantee", "msg_scope_private_parent_collab", "msg_scope_private_parent_sibling":
				candidate.GrantID = "authority_" + record.ID
				candidate.Reason = ScopeVisibilityReasonPrivateGrant
			}
			candidates = append(candidates, candidate)
		}
		return candidates, nil
	})
	require.NoError(t, err)
	require.Equal(t, []string{
		"msg_scope_private_parent_sibling",
		"msg_scope_private_parent_collab",
		"msg_scope_private_parent_grantee",
		"msg_scope_private_parent_owner",
	}, visibleMessageRefIDs(search.Messages))

	allowed, err := api.CanReadMessage(ctx, ScopeAPIReadCheckRequest{
		WorkspaceID:    workspaceID,
		MessageID:      "msg_scope_private_parent_collab",
		ViewerMemberID: privateAgentID,
		ViewerType:     ScopeActorPrivateAgent,
		RequestKind:    ScopeVisibilityRequestDiagnostics,
	})
	require.NoError(t, err)
	require.True(t, allowed.Allowed)
	require.Equal(t, ScopeVisibilityReasonPrivateGrant, allowed.Reason)

	siblingAllowed, err := api.CanReadMessage(ctx, ScopeAPIReadCheckRequest{
		WorkspaceID:    workspaceID,
		MessageID:      "msg_scope_private_parent_sibling",
		ViewerMemberID: privateAgentID,
		ViewerType:     ScopeActorPrivateAgent,
		RequestKind:    ScopeVisibilityRequestDiagnostics,
	})
	require.NoError(t, err)
	require.True(t, siblingAllowed.Allowed)
	require.Equal(t, ScopeVisibilityReasonPrivateGrant, siblingAllowed.Reason)

	denied, err := api.CanReadMessage(ctx, ScopeAPIReadCheckRequest{
		WorkspaceID:    workspaceID,
		MessageID:      "msg_scope_private_parent_stranger",
		ViewerMemberID: privateAgentID,
		ViewerType:     ScopeActorPrivateAgent,
		RequestKind:    ScopeVisibilityRequestDiagnostics,
	})
	require.NoError(t, err)
	require.False(t, denied.Allowed)

	crossOwnerDenied, err := api.CanReadMessage(ctx, ScopeAPIReadCheckRequest{
		WorkspaceID:    workspaceID,
		MessageID:      "msg_scope_private_parent_cross_agent",
		ViewerMemberID: privateAgentID,
		ViewerType:     ScopeActorPrivateAgent,
		RequestKind:    ScopeVisibilityRequestDiagnostics,
	})
	require.NoError(t, err)
	require.False(t, crossOwnerDenied.Allowed)

	orphanChannelID := "scp_scope_private_parent_orphan_channel"
	orphanThreadID := "scp_scope_private_parent_orphan_thread"
	insertMessagingChannel(t, ctx, db, orphanChannelID, workspaceID, "private-parent-orphan", ownerMemberID, "", now)
	require.NoError(t, repos.Messages.Insert(ctx, scopePrivateAuthorityMessageForTest(workspaceID, orphanChannelID, "msg_scope_private_parent_orphan_root", 1, 100, ownerMemberID, ScopeActorHuman, now.Add(8*time.Second))))
	insertMessagingReplyThread(t, ctx, db, orphanThreadID, workspaceID, orphanChannelID, "msg_scope_private_parent_orphan_root", "scope-private-parent-orphan", "", now)
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: orphanThreadID, MemberID: privateAgentID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: orphanThreadID, MemberID: siblingAgentMemberID, Visibility: "private", OwnerMemberID: ownerMemberID, EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.Messages.Insert(ctx, scopePrivateAuthorityMessageForTest(workspaceID, orphanThreadID, "msg_scope_private_parent_orphan_sibling", 1, 110, siblingAgentMemberID, ScopeActorPrivateAgent, now.Add(9*time.Second))))

	orphanHistory, err := api.ListHistory(ctx, ScopeAPIHistoryRequest{
		WorkspaceID:    workspaceID,
		ScopeID:        orphanThreadID,
		ViewerMemberID: privateAgentID,
		ViewerType:     ScopeActorPrivateAgent,
		RequestKind:    ScopeVisibilityRequestHistory,
		Limit:          10,
	})
	require.NoError(t, err)
	require.NotContains(t, historyMessageIDs(orphanHistory.Messages), "msg_scope_private_parent_orphan_sibling")
}

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	repos := NewScopeRepositories(db)
	now := scopePrivateAuthorityNowForTest().Add(24 * time.Hour)

	workspaceID := "wsp_scope_parent_thread_read"
	channelID := "scp_scope_parent_thread_read_channel"
	threadID := "scp_scope_parent_thread_read_thread"
	ownerMemberID := "mem_scope_parent_thread_read_owner"
	viewerMemberID := "mem_scope_parent_thread_read_viewer"
	threadMessageID := "msg_scope_parent_thread_read_reply"

	insertMessagingWorkspace(t, ctx, db, workspaceID, now)
	insertMessagingUser(t, ctx, db, "usr_scope_parent_thread_read_owner", "scope-parent-thread-read-owner@example.com", now)
	insertMessagingHumanMember(t, ctx, db, ownerMemberID, workspaceID, "usr_scope_parent_thread_read_owner", now)
	insertMessagingUser(t, ctx, db, "usr_scope_parent_thread_read_viewer", "scope-parent-thread-read-viewer@example.com", now)
	insertMessagingHumanMember(t, ctx, db, viewerMemberID, workspaceID, "usr_scope_parent_thread_read_viewer", now)
	insertMessagingChannel(t, ctx, db, channelID, workspaceID, "parent-thread-read", ownerMemberID, "", now)
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: channelID, MemberID: viewerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	require.NoError(t, repos.Messages.Insert(ctx, scopePrivateAuthorityMessageForTest(workspaceID, channelID, "msg_scope_parent_thread_read_root", 1, 1, ownerMemberID, ScopeActorHuman, now.Add(time.Second))))
	insertMessagingReplyThread(t, ctx, db, threadID, workspaceID, channelID, "msg_scope_parent_thread_read_root", "parent-thread-readable", "", now)
	require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{ScopeID: threadID, MemberID: ownerMemberID, Visibility: "public", EffectiveFromSeq: 1, CreatedAt: now, UpdatedAt: now}))
	threadMessage := scopePrivateAuthorityMessageForTest(workspaceID, threadID, threadMessageID, 1, 2, ownerMemberID, ScopeActorHuman, now.Add(2*time.Second))
	require.NoError(t, repos.Messages.Insert(ctx, threadMessage))
	require.NoError(t, repos.Search.InsertDoc(ctx, ScopeSearchDocRecord{
		MessageID:   threadMessage.ID,
		WorkspaceID: threadMessage.WorkspaceID,
		ScopeID:     threadMessage.ScopeID,
		GlobalSeq:   threadMessage.WorkspaceGlobalSeq,
		Content:     "parent-readable-thread-needle",
		CreatedAt:   threadMessage.CreatedAt,
	}))

	api := NewScopeAPIService(New(db, nil, 0, Options{}))
	history, err := api.ListHistory(ctx, ScopeAPIHistoryRequest{
		WorkspaceID:    workspaceID,
		ScopeID:        threadID,
		ViewerMemberID: viewerMemberID,
		ViewerType:     ScopeActorHuman,
		RequestKind:    ScopeVisibilityRequestHistory,
		Limit:          10,
	})
	require.NoError(t, err)
	require.Equal(t, []string{threadMessageID}, historyMessageIDs(history.Messages))

	readCheck, err := api.CanReadMessage(ctx, ScopeAPIReadCheckRequest{
		WorkspaceID:    workspaceID,
		MessageID:      threadMessageID,
		ViewerMemberID: viewerMemberID,
		ViewerType:     ScopeActorHuman,
		RequestKind:    ScopeVisibilityRequestAttachment,
	})
	require.NoError(t, err)
	require.True(t, readCheck.Allowed)
	require.Equal(t, ScopeVisibilityReasonMembership, readCheck.Reason)
	sidecarDecisions, err := api.AuthorizeSidecarReadBatch(ctx, SidecarReadBatchRequest{
		WorkspaceID: workspaceID,
		Kind:        SidecarKindSourceRef,
		Items: []SidecarReadBatchItem{{
			RequestID:      "source-ref",
			MessageID:      threadMessageID,
			ViewerMemberID: viewerMemberID,
		}},
	})
	require.NoError(t, err)
	require.True(t, sidecarDecisions["source-ref"].Allowed)
	require.Equal(t, ScopeVisibilityReasonMembership, sidecarDecisions["source-ref"].Reason)

	var outboxRows int
	require.NoError(t, db.QueryRow(ctx, `SELECT COUNT(*) FROM scope_fanout_outbox WHERE workspace_id = $1`, workspaceID).Scan(&outboxRows))
	require.Zero(t, outboxRows, "active read authority must not create delivery/fanout rows")
}

func TestScopeReadScopesUseSQLReadablePredicate(t *testing.T) {
	require.Contains(t, scopeMessagesVisibleToMemberQuery.SQL, scopeReadableMessagePredicateSQL)
	require.Contains(t, scopeSearchVisibilityCandidatesQuery.SQL, scopeReadableMessagePredicateSQL)
	require.Contains(t, scopeMessageVisibilityByIDQuery.SQL, scopeReadableMessagePredicateSQL)
	require.Contains(t, scopeVisibleAuthorMemberIDsQuery.SQL, scopeReadableMessagePredicateSQL)
	require.Contains(t, scopeMessagesVisibleToMemberQuery.SQL, scopeReadableMessageReasonSQL)
	require.Contains(t, scopeSearchVisibilityCandidatesQuery.SQL, scopeReadableMessageReasonSQL)
	require.Contains(t, scopeMessageVisibilityByIDQuery.SQL, scopeReadableMessageReasonSQL)
	require.Contains(t, scopeMessagesVisibleToMemberQuery.SQL, scopePrivateViewerMemberSQL)
	require.Contains(t, scopeSearchVisibilityCandidatesQuery.SQL, scopePrivateViewerMemberSQL)
	require.Contains(t, scopeMessageVisibilityByIDQuery.SQL, scopePrivateViewerMemberSQL)
	for _, query := range []string{
		scopeMessagesVisibleToMemberQuery.SQL,
		scopeSearchVisibilityCandidatesQuery.SQL,
		scopeMessageVisibilityByIDQuery.SQL,
		scopeVisibleAuthorMemberIDsQuery.SQL,
	} {
		require.Contains(t, query, "message_scope.parent_scope_id")
		require.Contains(t, query, "task_read_source.board_id")
		require.Contains(t, query, "task_parent_authority_grant")
		require.Contains(t, query, "task_parent_authority_participant")
		require.Contains(t, query, "m.created_at >= COALESCE(sm.authority_from_at, sm.created_at)")
		require.Contains(t, query, "task_parent_source_thread.parent_scope_id = source_scope.id")
	}
	require.NotContains(t, scope_visibilitySourceForTest(t), "ScopeVisibilityRequest"+"Receive")
	require.NotContains(t, scope_visibilitySourceForTest(t), "request.RequestKind) ==")
}

func scope_visibilitySourceForTest(t *testing.T) string {
	t.Helper()
	content, err := os.ReadFile("scope_visibility.go")
	require.NoError(t, err)
	return string(content)
}

func visibilityCandidateMessageIDs(candidates []ScopeMessageVisibilityCandidate) []string {
	ids := make([]string, 0, len(candidates))
	for _, candidate := range candidates {
		ids = append(ids, candidate.Message.ID)
	}
	return ids
}

func insertScopeTaskCollaboratorGrantForMessageTest(t *testing.T, ctx context.Context, db coredb.QueryExecer, id string, workspaceID string, ownerMemberID string, scopeType string, scopeID string, collaboratorMemberID string, now time.Time) {
	t.Helper()

	executionID := "tse_" + id
	collaboratorID := "tspm_" + id
	mustExecMessaging(t, ctx, db, `
INSERT INTO task_scope_executions (
    id, workspace_id, owner_member_id, scope_type, scope_id, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $6)
`, executionID, workspaceID, ownerMemberID, scopeType, scopeID, now)
	mustExecMessaging(t, ctx, db, `
INSERT INTO task_scope_participant_metadata (
    id, owner_scope_execution_id, member_id, active_key, role,
    added_by_member_id, added_at, add_notification_state, expires_at, created_at, updated_at
) VALUES ($1, $2, $3, $4, 'contributor', $5, $6, 'none', NULL, $6, $6)
`, collaboratorID, executionID, collaboratorMemberID, "active_"+collaboratorID, ownerMemberID, now)
}

func scopePrivateAuthorityMessageForTest(workspaceID string, scopeID string, messageID string, seq int64, globalSeq int64, authorMemberID string, authorType string, createdAt time.Time) ScopeMessageRecord {
	return ScopeMessageRecord{ID: messageID, WorkspaceID: workspaceID, ScopeID: scopeID, Seq: seq, WorkspaceGlobalSeq: globalSeq, AuthorMemberID: authorMemberID, AuthorType: authorType, MessageType: "text", Content: messageID, CreatedAt: createdAt, UpdatedAt: createdAt}
}

func scopePrivateAuthorityNowForTest() time.Time {
	return time.Date(2026, 5, 13, 6, 0, 0, 0, time.UTC)
}
func TestTaskParentAuthorityPolicyModesAreExplicit(t *testing.T) {
	t.Parallel()

	require.NotContains(t, taskLiveParentAuthoritySQL, "TRUE OR")
	require.NotContains(t, taskHistoricalParentAuthoritySQL, "TRUE OR")
	require.NotContains(t, taskLiveParentAuthoritySQL, "task_parent_authority_grant")
	require.NotContains(t, taskLiveParentAuthoritySQL, "task_parent_authority_participant")
	require.Contains(t, taskHistoricalParentAuthoritySQL, "task_parent_authority_grant")
	require.Contains(t, taskHistoricalParentAuthoritySQL, "task_parent_authority_participant")
	require.Contains(t, taskLiveParentAuthoritySQL, "CURRENT_TIMESTAMP")
	require.Contains(t, taskHistoricalParentAuthoritySQL, "m.created_at")

	base := taskParentAuthorityRelationOptions{
		TaskAlias:        "task",
		BoardAlias:       "board",
		ChannelAlias:     "channel",
		MembershipAlias:  "membership",
		GranteeMemberSQL: "$2",
		AuthorMemberSQL:  "author.member_id",
		EventAtSQL:       "author.created_at",
		PolicyMode:       taskParentAuthorityPolicyHistorical,
	}
	require.NotEqual(t, "(FALSE)", taskParentAuthorityRelationSQL(base))

	invalid := base
	invalid.PolicyMode = taskParentAuthorityPolicyMode("unknown")
	require.Equal(t, "(FALSE)", taskParentAuthorityRelationSQL(invalid))

	live := base
	live.AuthorMemberSQL = ""
	live.EventAtSQL = "CURRENT_TIMESTAMP"
	live.PolicyMode = taskParentAuthorityPolicyLive
	liveSQL := taskParentAuthorityRelationSQL(live)
	require.NotEqual(t, "(FALSE)", liveSQL)
	require.NotContains(t, liveSQL, "task_parent_authority_grant")
	require.NotContains(t, liveSQL, "task_parent_authority_participant")
	require.Contains(t, liveSQL, "CURRENT_TIMESTAMP")
}
