package taskboard

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/testfixture"
)

func TestAgentTaskAssignmentCanReferenceSameOwnerPrivateSiblingAgent(t *testing.T) {
	ctx := context.Background()
	pool := startTaskboardTestPool(t, ctx)

	now := time.Date(2026, 5, 15, 5, 20, 0, 0, time.UTC)
	workspaceID := "wsp_task_sibling_assign"
	ownerUserID := "usr_task_sibling_owner"
	ownerMemberID := "mem_task_sibling_owner"
	otherOwnerMemberID := "mem_task_sibling_other_owner"
	machineID := "mach_task_sibling_assign"
	channelID := "chn_task_sibling_assign"
	actingAgentID := "agt_task_sibling_actor"
	actingAgentMemberID := "mem_task_sibling_actor"
	siblingAgentID := "agt_task_sibling_target"
	siblingAgentMemberID := "mem_task_sibling_target"
	otherAgentID := "agt_task_sibling_other"
	otherAgentMemberID := "mem_task_sibling_other"

	mustExecTaskboard(t, ctx, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'task-sibling-owner@example.com', 'task-sibling-owner', 'Task Sibling Owner', '', FALSE, 'hash', $2, $2, $2)
`, ownerUserID, now)
	mustExecTaskboard(t, ctx, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'task-sibling-assign', 'Task Sibling Assign', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, pool, workspaceID, now), now)
	mustExecTaskboard(t, ctx, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, status, created_at, updated_at)
VALUES
  ($1, $6, 'human', 'Task Sibling Owner', $7, 'owner', 'active', $8, $8),
  ($2, $6, 'human', 'Other Sibling Owner', NULL, 'member', 'active', $8, $8),
  ($3, $6, 'agent', 'Acting Private Agent', NULL, NULL, 'active', $8, $8),
  ($4, $6, 'agent', 'Sibling Private Agent', NULL, NULL, 'active', $8, $8),
  ($5, $6, 'agent', 'Other Owner Private Agent', NULL, NULL, 'active', $8, $8)
`, ownerMemberID, otherOwnerMemberID, actingAgentMemberID, siblingAgentMemberID, otherAgentMemberID, workspaceID, ownerUserID, now)
	mustExecTaskboard(t, ctx, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, connected_at, created_at, updated_at)
VALUES ($1, $2, $3, 'task-sibling-machine', 'api_key_task_sibling', $4, $4, $4)
`, machineID, workspaceID, ownerUserID, now)
	mustExecTaskboard(t, ctx, pool, `
INSERT INTO agents (id, workspace_id, machine_id, member_id, name, avatar_url, runtime, model, reasoning_effort, description, session_id, auto_wake_enabled, status, activity, activity_detail, created_at, updated_at)
VALUES
  ($1, $7, $8, $2, 'acting-private-agent', '', 'codex', 'gpt-5.4', '', '', '', TRUE, 'active', 'online', '', $9, $9),
  ($3, $7, $8, $4, 'sibling-private-agent', '', 'codex', 'gpt-5.4', '', '', '', TRUE, 'active', 'online', '', $9, $9),
  ($5, $7, $8, $6, 'other-owner-private-agent', '', 'codex', 'gpt-5.4', '', '', '', TRUE, 'active', 'online', '', $9, $9)
`, actingAgentID, actingAgentMemberID, siblingAgentID, siblingAgentMemberID, otherAgentID, otherAgentMemberID, workspaceID, machineID, now)
	seedTaskboardChannelScope(t, ctx, pool, workspaceID, channelID, "all", "", "", "public", "", now)
	mustExecTaskboard(t, ctx, pool, `
INSERT IGNORE INTO scope_memberships (
  scope_id, member_id, visibility, owner_member_id, last_read_seq, last_read_global_seq,
  owner_context_dismissed_through_global_seq, created_at, updated_at, removed_at, archived_at
)
VALUES
  ($1, $2, 'public', NULL, 0, 0, 0, $7, $7, NULL, NULL),
  ($1, $3, 'private', $2, 0, 0, 0, $7, $7, NULL, NULL),
  ($1, $4, 'private', $2, 0, 0, 0, $7, $7, NULL, NULL),
  ($1, $5, 'private', $6, 0, 0, 0, $7, $7, NULL, NULL)
`, channelID, ownerMemberID, actingAgentMemberID, siblingAgentMemberID, otherAgentMemberID, otherOwnerMemberID, now)
	seedTaskboardChannelScopeFacts(t, ctx, pool, workspaceID, now)

	service := New(pool)
	created, err := service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewAgentServiceContext(workspaceID, actingAgentID, actingAgentMemberID),
		Board:   BoardRef{Channel: "#all"},
		Titles:  []string{"Assign sibling private agent from task_update"},
	})
	require.NoError(t, err)
	require.Len(t, created.Tasks, 1)

	options, err := service.ListTaskOptions(ctx, ListTaskOptionsRequest{
		Context: NewAgentServiceContext(workspaceID, actingAgentID, actingAgentMemberID),
		Board:   BoardRef{Channel: "#all"},
	})
	require.NoError(t, err)
	requireTaskMemberOption(t, options.AssigneeOptions, siblingAgentMemberID, siblingAgentID)
	requireNoTaskMemberOption(t, options.AssigneeOptions, otherAgentMemberID)

	updated, err := service.UpdateTask(ctx, UpdateTaskRequest{
		Context: NewAgentServiceContext(workspaceID, actingAgentID, actingAgentMemberID),
		Board:   BoardRef{Channel: "#all"},
		Task:    TaskRef{TaskNumber: created.Tasks[0].TaskNumber},
		Patch: TaskUpdatePatch{
			AssigneeRef: taskboardTestStringPtr(siblingAgentID),
		},
	})
	require.NoError(t, err)
	require.NotNil(t, updated.Task.AssigneeProfile)
	require.Equal(t, siblingAgentID, updated.Task.AssigneeProfile.PrincipalID)

	assigned, err := service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewAgentServiceContext(workspaceID, actingAgentID, actingAgentMemberID),
		Board:   BoardRef{Channel: "#all"},
		Task: &TaskCreateInput{
			Title:       "Assign sibling private agent at creation",
			AssigneeRef: "@sibling-private-agent",
		},
	})
	require.NoError(t, err)
	require.Len(t, assigned.Tasks, 1)
	require.NotNil(t, assigned.Tasks[0].AssigneeProfile)
	require.Equal(t, siblingAgentID, assigned.Tasks[0].AssigneeProfile.PrincipalID)
}

func requireTaskMemberOption(t *testing.T, options []TaskMemberOption, memberID string, agentID string) {
	t.Helper()
	for _, option := range options {
		if option.MemberID == memberID {
			require.Equal(t, agentID, option.AgentID)
			return
		}
	}
	require.Failf(t, "task member option not found", "memberID=%s agentID=%s options=%+v", memberID, agentID, options)
}

func requireNoTaskMemberOption(t *testing.T, options []TaskMemberOption, memberID string) {
	t.Helper()
	for _, option := range options {
		require.NotEqual(t, memberID, option.MemberID)
	}
}

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