package taskboard

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestTaskReviewPersistsReviewRequestHistory(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	createdResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Review-ready task"},
	})
	require.NoError(t, err)
	require.Len(t, createdResponse.Tasks, 1)

	taskID := createdResponse.Tasks[0].ID
	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.NoError(t, err)

	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: taskID}, StatusInProgress)

	firstReview, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Ready for the first review pass",
			Questions:           []string{"Does the review target look complete?"},
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:review-ready"}`,
		},
	})
	require.NoError(t, err)
	require.NotEmpty(t, firstReview.Review.ID)

	changesResponse, err := fixture.service.RequestTaskChanges(ctx, RequestTaskChangesRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.reviewerUserID, fixture.reviewerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewDecisionInput{
			ReviewID:  firstReview.Review.ID,
			Decision:  "Please attach the missing regression summary.",
			Summary:   "Need one more artifact before approval.",
			Questions: []string{"Can you include the targeted test output?"},
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInProgress), changesResponse.Task.Status)

	secondReview, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Re-requesting review with the extra artifact attached",
			Questions:           []string{"Is the test proof sufficient now?"},
			ReviewTargetType:    "task_artifact_set",
			ReviewTargetPayload: `{"artifactIds":["art_test_summary"]}`,
		},
	})
	require.NoError(t, err)
	require.NotEmpty(t, secondReview.Review.ID)

	approveResponse, err := fixture.service.ApproveTaskReview(ctx, ApproveTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.reviewerUserID, fixture.reviewerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewDecisionInput{
			ReviewID: secondReview.Review.ID,
			Decision: "Looks good to ship.",
			Summary:  "Approved after the extra artifact landed.",
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusDone), approveResponse.Task.Status)

	detailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
	})
	require.NoError(t, err)
	require.Len(t, detailResponse.Detail.ReviewHistory, 2)
	require.Equal(t, "changes_requested", detailResponse.Detail.ReviewHistory[0].Status)
	require.Equal(t, "approved", detailResponse.Detail.ReviewHistory[1].Status)
	for _, review := range detailResponse.Detail.ReviewHistory {
		require.NotNil(t, review.RequestedByProfile)
		require.Equal(t, fixture.ownerMemberID, review.RequestedByProfile.MemberID)
		require.Equal(t, fixture.ownerUserID, review.RequestedByProfile.PrincipalID)
		require.NotNil(t, review.ReviewerProfile)
		require.Equal(t, fixture.reviewerMemberID, review.ReviewerProfile.MemberID)
		require.Equal(t, fixture.reviewerUserID, review.ReviewerProfile.PrincipalID)
	}
	require.NotNil(t, detailResponse.Detail.ReviewHistory[0].DecidedByProfile)
	require.Equal(t, fixture.reviewerMemberID, detailResponse.Detail.ReviewHistory[0].DecidedByProfile.MemberID)
	require.NotNil(t, detailResponse.Detail.ReviewHistory[1].DecidedByProfile)
	require.Equal(t, fixture.reviewerMemberID, detailResponse.Detail.ReviewHistory[1].DecidedByProfile.MemberID)

	reviewRows, err := fixture.pool.Query(ctx, `
SELECT status, requested_by_ref, reviewer_ref, review_target_type, decision_md
FROM task_review_requests
WHERE task_id = $1
ORDER BY requested_at ASC
`, taskID)
	require.NoError(t, err)
	t.Cleanup(reviewRows.Close)

	var reviewStatuses []string
	var reviewDecisions []string
	for reviewRows.Next() {
		var status string
		var requestedByRef string
		var reviewerRef string
		var reviewTargetType string
		var decision string
		require.NoError(t, reviewRows.Scan(&status, &requestedByRef, &reviewerRef, &reviewTargetType, &decision))
		reviewStatuses = append(reviewStatuses, status)
		reviewDecisions = append(reviewDecisions, decision)
		require.Equal(t, fixture.ownerMemberID, requestedByRef)
		require.Equal(t, fixture.reviewerMemberID, reviewerRef)
		require.NotEmpty(t, reviewTargetType)
	}
	require.NoError(t, reviewRows.Err())
	require.Equal(t, []string{"changes_requested", "approved"}, reviewStatuses)
	require.Equal(t, []string{"Please attach the missing regression summary.", "Looks good to ship."}, reviewDecisions)

	eventRows, err := fixture.pool.Query(ctx, `
SELECT JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.type'))
FROM tasks t
JOIN scope_activity_events ae
  ON ae.scope_id = t.task_scope_id
 AND ae.activity_ref_type = 'task_event'
WHERE t.id = $1
  AND ae.activity_type LIKE 'task.review_%'
ORDER BY ae.seq ASC, ae.id ASC
`, taskID)
	require.NoError(t, err)
	t.Cleanup(eventRows.Close)

	var eventTypes []string
	for eventRows.Next() {
		var eventType string
		require.NoError(t, eventRows.Scan(&eventType))
		eventTypes = append(eventTypes, eventType)
	}
	require.NoError(t, eventRows.Err())
	require.Equal(t, []string{"review_requested", "review_changes_requested", "review_requested", "review_approved"}, eventTypes)

	statusEventRows, err := fixture.pool.Query(ctx, `
SELECT JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.payload'))
FROM tasks t
JOIN scope_activity_events ae
  ON ae.scope_id = t.task_scope_id
 AND ae.activity_ref_type = 'task_event'
WHERE t.id = $1
  AND JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.type')) = 'status_changed'
ORDER BY ae.seq ASC, ae.id ASC
	`, taskID)
	require.NoError(t, err)
	t.Cleanup(statusEventRows.Close)

	var statusEventPayloads []string
	for statusEventRows.Next() {
		var payload string
		require.NoError(t, statusEventRows.Scan(&payload))
		statusEventPayloads = append(statusEventPayloads, payload)
	}
	require.NoError(t, statusEventRows.Err())
	require.Equal(t, []string{"in_progress", "in_review", "in_progress", "in_review", "done"}, statusEventPayloads)
}

func TestDuplicateTaskReviewRequestReturnsExistingPendingReview(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	createdResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Avoid duplicate review churn"},
	})
	require.NoError(t, err)
	require.Len(t, createdResponse.Tasks, 1)

	taskID := createdResponse.Tasks[0].ID
	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.NoError(t, err)
	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: taskID}, StatusInProgress)

	request := RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Please review the regression coverage.",
			Questions:           []string{"Did we cover the retry path?"},
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:duplicate-review"}`,
		},
	}

	firstReview, err := fixture.service.RequestTaskReview(ctx, request)
	require.NoError(t, err)
	secondReview, err := fixture.service.RequestTaskReview(ctx, request)
	require.NoError(t, err)

	require.Equal(t, firstReview.Review.ID, secondReview.Review.ID)
	require.Equal(t, firstReview.Review.RequestedAt, secondReview.Review.RequestedAt)
	require.Equal(t, string(StatusInReview), secondReview.Task.Status)

	var totalReviews int
	require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM task_review_requests
WHERE task_id = $1
`, taskID).Scan(&totalReviews))
	require.Equal(t, 1, totalReviews)

	var pendingReviews int
	require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM task_review_requests
WHERE task_id = $1
  AND status = 'requested'
`, taskID).Scan(&pendingReviews))
	require.Equal(t, 1, pendingReviews)

	var requestedEvents int
	require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM tasks t
JOIN scope_activity_events ae
  ON ae.scope_id = t.task_scope_id
 AND ae.activity_ref_type = 'task_event'
WHERE t.id = $1
  AND JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.type')) = 'review_requested'
`, taskID).Scan(&requestedEvents))
	require.Equal(t, 1, requestedEvents)
}

func TestAgentCanCompleteBareInReviewTaskWithoutReviewRequest(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)
	agentContext := NewAgentServiceContext(fixture.workspaceID, fixture.agentID, fixture.agentMemberID)

	createdResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: agentContext,
		Board:   BoardRef{Channel: "#all"},
		Titles:  []string{"Agent task without a review request"},
	})
	require.NoError(t, err)
	require.Len(t, createdResponse.Tasks, 1)
	taskNumber := createdResponse.Tasks[0].TaskNumber

	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: agentContext,
		Board:   BoardRef{Channel: "#all"},
		Tasks:   []TaskRef{{TaskNumber: taskNumber}},
	})
	require.NoError(t, err)

	executeNextStatusProgressionForTest(t, ctx, fixture.service, agentContext, BoardRef{Channel: "#all"}, TaskRef{TaskNumber: taskNumber})
	executeNextStatusProgressionForTest(t, ctx, fixture.service, agentContext, BoardRef{Channel: "#all"}, TaskRef{TaskNumber: taskNumber})
	doneResponse := executeNextStatusProgressionForTest(t, ctx, fixture.service, agentContext, BoardRef{Channel: "#all"}, TaskRef{TaskNumber: taskNumber})
	require.True(t, doneResponse.Changed)
	require.Equal(t, string(StatusDone), doneResponse.Task.Status)
}

func TestPendingReviewIsSupersededWhenStatusCompletesByDefault(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	createdResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Pending review cannot be bypassed"},
	})
	require.NoError(t, err)
	require.Len(t, createdResponse.Tasks, 1)
	taskID := createdResponse.Tasks[0].ID

	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.NoError(t, err)

	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: taskID}, StatusInProgress)

	requestedReview, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Please review before completion",
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:pending-review"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, taskReviewStatusRequested, requestedReview.Review.Status)

	queueBeforeCompletion, err := fixture.service.ListTaskReviewQueue(ctx, ListTaskReviewQueueRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.reviewerUserID, fixture.reviewerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
	})
	require.NoError(t, err)
	require.Len(t, queueBeforeCompletion.Reviews, 1)

	doneResponse := moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: taskID}, StatusDone)
	require.True(t, doneResponse.Changed)
	require.Equal(t, string(StatusDone), doneResponse.Task.Status)

	detailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
	})
	require.NoError(t, err)
	require.Len(t, detailResponse.Detail.ReviewHistory, 1)
	require.Equal(t, taskReviewStatusSuperseded, detailResponse.Detail.ReviewHistory[0].Status)
	require.Equal(t, fixture.ownerMemberID, detailResponse.Detail.ReviewHistory[0].DecidedByRef)
	require.NotNil(t, detailResponse.Detail.ReviewHistory[0].DecidedAt)

	queueAfterCompletion, err := fixture.service.ListTaskReviewQueue(ctx, ListTaskReviewQueueRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.reviewerUserID, fixture.reviewerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
	})
	require.NoError(t, err)
	require.Empty(t, queueAfterCompletion.Reviews)

	_, err = fixture.service.RequestTaskChanges(ctx, RequestTaskChangesRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.reviewerUserID, fixture.reviewerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewDecisionInput{
			ReviewID: requestedReview.Review.ID,
			Decision: "This should not reopen a completed task.",
		},
	})
	require.Error(t, err)
	require.True(t, errors.Is(err, ErrConflict))
}

func TestReviewerCanLoadTaskDetailAfterReviewIsRequested(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	createdResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Reviewer detail access"},
	})
	require.NoError(t, err)
	require.Len(t, createdResponse.Tasks, 1)

	taskID := createdResponse.Tasks[0].ID
	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.NoError(t, err)

	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: taskID}, StatusInProgress)

	requestedReview, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Ready for reviewer inspection",
			Questions:           []string{"Does the review target look complete?"},
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:review-ready"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), requestedReview.Task.Status)

	detailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.reviewerUserID, fixture.reviewerDisplayName),
		Task:    TaskRef{TaskID: taskID},
	})
	require.NoError(t, err)
	require.Equal(t, "Ready for reviewer inspection", detailResponse.Detail.ReviewHistory[0].Summary)
}

func TestTaskReviewRequestAcceptsAtPrefixedReviewerRef(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	createdResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Reviewer ref normalization"},
	})
	require.NoError(t, err)
	require.Len(t, createdResponse.Tasks, 1)

	taskID := createdResponse.Tasks[0].ID
	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.NoError(t, err)

	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: taskID}, StatusInProgress)

	reviewResponse, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         "@" + fixture.reviewerMemberID,
			Summary:             "Reviewer can be addressed with @member",
			Questions:           []string{"Does the task packet look complete?"},
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:review-ready"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, fixture.reviewerMemberID, reviewResponse.Review.ReviewerRef)
	require.NotNil(t, reviewResponse.Task.AssigneeProfile)
	require.Equal(t, fixture.ownerMemberID, reviewResponse.Task.AssigneeProfile.MemberID)

	var assigneeRef string
	require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT assignee_member_id
FROM tasks
WHERE id = $1
`, taskID).Scan(&assigneeRef))
	require.Equal(t, fixture.ownerMemberID, assigneeRef)
}

func TestTaskReviewSupportsReassignAndNotReviewableYet(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	createdResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Review management task"},
	})
	require.NoError(t, err)
	require.Len(t, createdResponse.Tasks, 1)

	taskID := createdResponse.Tasks[0].ID
	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.NoError(t, err)

	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: taskID}, StatusInProgress)

	assignedEventsBeforeReview := countTaskEventType(t, ctx, fixture, taskID, taskEventAssigned)

	requestedReview, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Ready for reviewer inspection",
			Questions:           []string{"Does the review target look complete?"},
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:review-ready"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), requestedReview.Task.Status)
	require.NotNil(t, requestedReview.Task.AssigneeProfile)
	require.Equal(t, fixture.ownerMemberID, requestedReview.Task.AssigneeProfile.MemberID)
	require.Equal(t, assignedEventsBeforeReview, countTaskEventType(t, ctx, fixture, taskID, taskEventAssigned))

	noopReassignReview, err := fixture.service.ReassignTaskReview(ctx, ReassignTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewReassignmentInput{
			ReviewID:    requestedReview.Review.ID,
			ReviewerRef: fixture.reviewerMemberID,
		},
	})
	require.NoError(t, err)
	require.Equal(t, fixture.reviewerMemberID, noopReassignReview.Review.ReviewerRef)
	require.NotNil(t, noopReassignReview.Review.RequestedByProfile)
	require.Equal(t, fixture.ownerMemberID, noopReassignReview.Review.RequestedByProfile.MemberID)
	require.Equal(t, fixture.ownerUserID, noopReassignReview.Review.RequestedByProfile.PrincipalID)
	require.NotNil(t, noopReassignReview.Review.ReviewerProfile)
	require.Equal(t, fixture.reviewerMemberID, noopReassignReview.Review.ReviewerProfile.MemberID)
	require.Equal(t, fixture.reviewerUserID, noopReassignReview.Review.ReviewerProfile.PrincipalID)
	require.NotNil(t, noopReassignReview.Task.AssigneeProfile)
	require.Equal(t, fixture.ownerMemberID, noopReassignReview.Task.AssigneeProfile.MemberID)
	require.Equal(t, assignedEventsBeforeReview, countTaskEventType(t, ctx, fixture, taskID, taskEventAssigned))

	reassignedReview, err := fixture.service.ReassignTaskReview(ctx, ReassignTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewReassignmentInput{
			ReviewID:    requestedReview.Review.ID,
			ReviewerRef: fixture.ownerMemberID,
		},
	})
	require.NoError(t, err)
	require.Equal(t, fixture.ownerMemberID, reassignedReview.Review.ReviewerRef)
	require.NotNil(t, reassignedReview.Task.AssigneeProfile)
	require.Equal(t, fixture.ownerUserID, reassignedReview.Task.AssigneeProfile.PrincipalID)
	require.Equal(t, assignedEventsBeforeReview, countTaskEventType(t, ctx, fixture, taskID, taskEventAssigned))

	cancelledReview, err := fixture.service.MarkTaskReviewNotReviewable(ctx, MarkTaskReviewNotReviewableRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewCancellationInput{
			ReviewID: requestedReview.Review.ID,
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInProgress), cancelledReview.Task.Status)
	require.Equal(t, taskReviewStatusCancelled, cancelledReview.Review.Status)
	require.Equal(t, fixture.ownerMemberID, cancelledReview.Review.DecidedByRef)
	require.NotNil(t, cancelledReview.Review.DecidedAt)

	detailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: taskID},
	})
	require.NoError(t, err)
	require.Len(t, detailResponse.Detail.ReviewHistory, 1)
	require.Equal(t, fixture.ownerMemberID, detailResponse.Detail.ReviewHistory[0].ReviewerRef)
	require.Equal(t, taskReviewStatusCancelled, detailResponse.Detail.ReviewHistory[0].Status)

	eventRows, err := fixture.pool.Query(ctx, `
SELECT JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.type'))
FROM tasks t
JOIN scope_activity_events ae
  ON ae.scope_id = t.task_scope_id
 AND ae.activity_ref_type = 'task_event'
WHERE t.id = $1
  AND ae.activity_type LIKE 'task.review%'
ORDER BY ae.seq ASC, ae.id ASC
`, taskID)
	require.NoError(t, err)
	t.Cleanup(eventRows.Close)

	var eventTypes []string
	for eventRows.Next() {
		var eventType string
		require.NoError(t, eventRows.Scan(&eventType))
		eventTypes = append(eventTypes, eventType)
	}
	require.NoError(t, eventRows.Err())
	require.Equal(t, []string{"review_requested", "reviewer_assigned", "review_cancelled"}, eventTypes)

	statusEventRows, err := fixture.pool.Query(ctx, `
SELECT JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.payload'))
FROM tasks t
JOIN scope_activity_events ae
  ON ae.scope_id = t.task_scope_id
 AND ae.activity_ref_type = 'task_event'
WHERE t.id = $1
  AND JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.type')) = 'status_changed'
ORDER BY ae.seq ASC, ae.id ASC
	`, taskID)
	require.NoError(t, err)
	t.Cleanup(statusEventRows.Close)

	var statusEventPayloads []string
	for statusEventRows.Next() {
		var payload string
		require.NoError(t, statusEventRows.Scan(&payload))
		statusEventPayloads = append(statusEventPayloads, payload)
	}
	require.NoError(t, statusEventRows.Err())
	require.Equal(t, []string{"in_progress", "in_review", "in_progress"}, statusEventPayloads)
}

func countTaskEventType(t *testing.T, ctx context.Context, fixture taskboardUserFixture, taskID string, eventType string) int {
	t.Helper()

	var count int
	require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT COUNT(*)
FROM tasks t
JOIN scope_activity_events ae
  ON ae.scope_id = t.task_scope_id
 AND ae.activity_ref_type = 'task_event'
WHERE t.id = $1
  AND JSON_UNQUOTE(JSON_EXTRACT(ae.payload, '$.taskEvent.type')) = $2
`, taskID, eventType).Scan(&count))
	return count
}

func TestParentStatusTransitionSupersedesChildRequestedReview(t *testing.T) {
	testCases := []struct {
		name          string
		parentStatus  Status
		expectedLabel string
	}{
		{
			name:          "parent enters in review",
			parentStatus:  StatusInReview,
			expectedLabel: string(StatusInReview),
		},
		{
			name:          "parent completes",
			parentStatus:  StatusDone,
			expectedLabel: string(StatusDone),
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			ctx := context.Background()
			fixture := seedTaskboardUserFixture(t, ctx)

			parentResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
				Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
				Board:   BoardRef{ChannelID: fixture.channelID},
				Titles:  []string{"Parent deliverable"},
			})
			require.NoError(t, err)
			require.Len(t, parentResponse.Tasks, 1)
			parentTaskID := parentResponse.Tasks[0].ID

			_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
				Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
				Tasks:   []TaskRef{{TaskID: parentTaskID}},
			})
			require.NoError(t, err)

			moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInProgress)

			subtaskResponse, err := fixture.service.CreateSubtasks(ctx, CreateSubtasksRequest{
				Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
				Task:    TaskRef{TaskID: parentTaskID},
				Titles:  []string{"Child execution unit"},
			})
			require.NoError(t, err)
			require.Len(t, subtaskResponse.Tasks, 1)
			childTaskID := subtaskResponse.Tasks[0].ID

			_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
				Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
				Tasks:   []TaskRef{{TaskID: childTaskID}},
			})
			require.NoError(t, err)

			moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: childTaskID}, StatusInProgress)

			reviewResponse, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
				Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
				Task:    TaskRef{TaskID: childTaskID},
				Review: TaskReviewRequestInput{
					ReviewerRef:         fixture.reviewerMemberID,
					Summary:             "Please review the child output.",
					ReviewTargetType:    "thread_only",
					ReviewTargetPayload: `{"threadRef":"#all:child-review"}`,
				},
			})
			require.NoError(t, err)
			require.Equal(t, taskReviewStatusRequested, reviewResponse.Review.Status)
			require.Equal(t, string(StatusInReview), reviewResponse.Task.Status)

			if tc.parentStatus == StatusDone {
				moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInReview)
			}

			parentStatusResponse := moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, tc.parentStatus)
			require.Equal(t, tc.expectedLabel, parentStatusResponse.Task.Status)

			childDetailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
				Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
				Task:    TaskRef{TaskID: childTaskID},
			})
			require.NoError(t, err)
			require.Equal(t, string(StatusDone), childDetailResponse.Detail.Task.Status)
			require.Len(t, childDetailResponse.Detail.ReviewHistory, 1)
			require.Equal(t, taskReviewStatusSuperseded, childDetailResponse.Detail.ReviewHistory[0].Status)
			require.Equal(t, fixture.ownerMemberID, childDetailResponse.Detail.ReviewHistory[0].DecidedByRef)
			require.NotNil(t, childDetailResponse.Detail.ReviewHistory[0].DecidedAt)

			var storedReviewStatus string
			require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT status
FROM task_review_requests
WHERE id = $1
`, reviewResponse.Review.ID).Scan(&storedReviewStatus))
			require.Equal(t, taskReviewStatusSuperseded, storedReviewStatus)

			var childTaskStatus string
			require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT status
FROM tasks
WHERE id = $1
`, childTaskID).Scan(&childTaskStatus))
			require.Equal(t, string(StatusDone), childTaskStatus)
		})
	}
}

func TestParentStatusTransitionSupersedesAllChildRequestedReviews(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	parentResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Parent review rollup"},
	})
	require.NoError(t, err)
	require.Len(t, parentResponse.Tasks, 1)
	parentTaskID := parentResponse.Tasks[0].ID

	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: parentTaskID}},
	})
	require.NoError(t, err)

	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInProgress)

	subtaskResponse, err := fixture.service.CreateSubtasks(ctx, CreateSubtasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: parentTaskID},
		Titles:  []string{"Child review unit 1", "Child review unit 2", "Child review unit 3"},
	})
	require.NoError(t, err)
	require.Len(t, subtaskResponse.Tasks, 3)

	childTaskIDs := make([]string, 0, len(subtaskResponse.Tasks))
	reviewIDs := make([]string, 0, len(subtaskResponse.Tasks))
	for index, subtask := range subtaskResponse.Tasks {
		childTaskIDs = append(childTaskIDs, subtask.ID)

		_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
			Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
			Tasks:   []TaskRef{{TaskID: subtask.ID}},
		})
		require.NoError(t, err)

		moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: subtask.ID}, StatusInProgress)

		reviewResponse, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
			Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
			Task:    TaskRef{TaskID: subtask.ID},
			Review: TaskReviewRequestInput{
				ReviewerRef:         fixture.reviewerMemberID,
				Summary:             "Please review child output.",
				ReviewTargetType:    "thread_only",
				ReviewTargetPayload: `{"threadRef":"#all:child-review-rollup"}`,
				Questions:           []string{"Is child output " + string(rune('A'+index)) + " ready?"},
			},
		})
		require.NoError(t, err)
		require.Equal(t, taskReviewStatusRequested, reviewResponse.Review.Status)
		reviewIDs = append(reviewIDs, reviewResponse.Review.ID)
	}

	parentStatusResponse := moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInReview)
	require.Equal(t, string(StatusInReview), parentStatusResponse.Task.Status)

	for _, childTaskID := range childTaskIDs {
		childDetailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
			Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
			Task:    TaskRef{TaskID: childTaskID},
		})
		require.NoError(t, err)
		require.Equal(t, string(StatusDone), childDetailResponse.Detail.Task.Status)
		require.Len(t, childDetailResponse.Detail.ReviewHistory, 1)
		require.Equal(t, taskReviewStatusSuperseded, childDetailResponse.Detail.ReviewHistory[0].Status)
		require.Equal(t, fixture.ownerMemberID, childDetailResponse.Detail.ReviewHistory[0].DecidedByRef)
	}

	for _, reviewID := range reviewIDs {
		var storedReviewStatus string
		require.NoError(t, fixture.pool.QueryRow(ctx, `
SELECT status
FROM task_review_requests
WHERE id = $1
`, reviewID).Scan(&storedReviewStatus))
		require.Equal(t, taskReviewStatusSuperseded, storedReviewStatus)
	}
}

func TestParentReviewAllowsIncompleteSubtasks(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	createClaimedParentWithTodoChild := func(parentTitle string, childTitle string) (string, string) {
		parentResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
			Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
			Board:   BoardRef{ChannelID: fixture.channelID},
			Titles:  []string{parentTitle},
		})
		require.NoError(t, err)
		require.Len(t, parentResponse.Tasks, 1)
		parentTaskID := parentResponse.Tasks[0].ID

		_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
			Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
			Tasks:   []TaskRef{{TaskID: parentTaskID}},
		})
		require.NoError(t, err)

		moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInProgress)

		subtaskResponse, err := fixture.service.CreateSubtasks(ctx, CreateSubtasksRequest{
			Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
			Task:    TaskRef{TaskID: parentTaskID},
			Titles:  []string{childTitle},
		})
		require.NoError(t, err)
		require.Len(t, subtaskResponse.Tasks, 1)
		return parentTaskID, subtaskResponse.Tasks[0].ID
	}

	reviewParentTaskID, reviewChildTaskID := createClaimedParentWithTodoChild("Parent with unfinished child", "Unfinished child")
	reviewResponse, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: reviewParentTaskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Parent is ready.",
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:parent-review"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), reviewResponse.Task.Status)

	reviewDetailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: reviewParentTaskID},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), reviewDetailResponse.Detail.Task.Status)
	require.Len(t, reviewDetailResponse.Detail.Subtasks, 1)
	require.Equal(t, reviewChildTaskID, reviewDetailResponse.Detail.Subtasks[0].ID)
	require.Equal(t, string(StatusTodo), reviewDetailResponse.Detail.Subtasks[0].Status)

	statusParentTaskID, statusChildTaskID := createClaimedParentWithTodoChild("Parent direct review transition", "Direct transition child")
	statusResponse := moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: statusParentTaskID}, StatusInReview)
	require.True(t, statusResponse.Changed)
	require.Equal(t, string(StatusInReview), statusResponse.Task.Status)

	detailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: statusParentTaskID},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), detailResponse.Detail.Task.Status)
	require.Len(t, detailResponse.Detail.Subtasks, 1)
	require.Equal(t, statusChildTaskID, detailResponse.Detail.Subtasks[0].ID)
	require.Equal(t, string(StatusTodo), detailResponse.Detail.Subtasks[0].Status)
}

func TestAgentParentStatusAllowsIncompleteSubtasks(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)
	agentContext := NewAgentServiceContext(fixture.workspaceID, fixture.agentID, fixture.agentMemberID)

	parentResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: agentContext,
		Board:   BoardRef{Channel: "#all"},
		Titles:  []string{"Agent parent with unfinished child"},
	})
	require.NoError(t, err)
	require.Len(t, parentResponse.Tasks, 1)
	parentTaskNumber := parentResponse.Tasks[0].TaskNumber

	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: agentContext,
		Board:   BoardRef{Channel: "#all"},
		Tasks:   []TaskRef{{TaskNumber: parentTaskNumber}},
	})
	require.NoError(t, err)
	executeNextStatusProgressionForTest(t, ctx, fixture.service, agentContext, BoardRef{Channel: "#all"}, TaskRef{TaskNumber: parentTaskNumber})

	subtaskResponse, err := fixture.service.CreateSubtasks(ctx, CreateSubtasksRequest{
		Context: agentContext,
		Board:   BoardRef{Channel: "#all"},
		Task:    TaskRef{TaskNumber: parentTaskNumber},
		Titles:  []string{"Agent unfinished child"},
	})
	require.NoError(t, err)
	require.Len(t, subtaskResponse.Tasks, 1)

	statusResponse := executeNextStatusProgressionForTest(t, ctx, fixture.service, agentContext, BoardRef{Channel: "#all"}, TaskRef{TaskNumber: parentTaskNumber})
	require.True(t, statusResponse.Changed)
	require.Equal(t, string(StatusInReview), statusResponse.Task.Status)

	detailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: agentContext,
		Board:   BoardRef{Channel: "#all"},
		Task:    TaskRef{TaskNumber: parentTaskNumber},
	})
	require.NoError(t, err)
	require.Len(t, detailResponse.Detail.Subtasks, 1)
	require.Equal(t, subtaskResponse.Tasks[0].ID, detailResponse.Detail.Subtasks[0].ID)
	require.Equal(t, string(StatusTodo), detailResponse.Detail.Subtasks[0].Status)
}

func TestParentReviewRequestSupersedesChildRequestedReviews(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	parentResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Parent review request rollup"},
	})
	require.NoError(t, err)
	require.Len(t, parentResponse.Tasks, 1)
	parentTaskID := parentResponse.Tasks[0].ID

	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: parentTaskID}},
	})
	require.NoError(t, err)
	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInProgress)

	subtaskResponse, err := fixture.service.CreateSubtasks(ctx, CreateSubtasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: parentTaskID},
		Titles:  []string{"Child ready for rollup"},
	})
	require.NoError(t, err)
	require.Len(t, subtaskResponse.Tasks, 1)
	childTaskID := subtaskResponse.Tasks[0].ID

	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: childTaskID}},
	})
	require.NoError(t, err)
	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: childTaskID}, StatusInProgress)
	childReview, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: childTaskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Child is ready.",
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:child-ready"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), childReview.Task.Status)

	parentReview, err := fixture.service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: parentTaskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         fixture.reviewerMemberID,
			Summary:             "Parent is ready.",
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:parent-ready"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), parentReview.Task.Status)

	childDetailResponse, err := fixture.service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: childTaskID},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusDone), childDetailResponse.Detail.Task.Status)
	require.Len(t, childDetailResponse.Detail.ReviewHistory, 1)
	require.Equal(t, taskReviewStatusSuperseded, childDetailResponse.Detail.ReviewHistory[0].Status)
}

func TestCreateSubtasksRejectsReviewedParent(t *testing.T) {
	ctx := context.Background()
	fixture := seedTaskboardUserFixture(t, ctx)

	parentResponse, err := fixture.service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Board:   BoardRef{ChannelID: fixture.channelID},
		Titles:  []string{"Reviewed parent"},
	})
	require.NoError(t, err)
	require.Len(t, parentResponse.Tasks, 1)
	parentTaskID := parentResponse.Tasks[0].ID

	_, err = fixture.service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Tasks:   []TaskRef{{TaskID: parentTaskID}},
	})
	require.NoError(t, err)
	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInProgress)
	moveTaskToDefaultStatusForTest(t, ctx, fixture.service, NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName), TaskRef{TaskID: parentTaskID}, StatusInReview)

	_, err = fixture.service.CreateSubtasks(ctx, CreateSubtasksRequest{
		Context: NewUserServiceContext(fixture.workspaceID, fixture.ownerUserID, fixture.ownerDisplayName),
		Task:    TaskRef{TaskID: parentTaskID},
		Titles:  []string{"Late child"},
	})
	require.Error(t, err)
	require.True(t, errors.Is(err, ErrConflict))
	require.Contains(t, err.Error(), "cannot create subtasks")
}
