package taskboard

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

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

	ctx := context.Background()
	dataDB := startTaskboardTiDBTestDB(t)

	now := time.Date(2026, 4, 4, 21, 0, 0, 0, time.UTC)
	workspaceID := "wsp_tidb_taskboard"
	userID := "usr_tidb_taskboard"
	reviewerUserID := "usr_tidb_taskboard_reviewer"
	memberID := "mem_tidb_taskboard"
	reviewerMemberID := "mem_tidb_taskboard_reviewer"
	agentMemberID := "mem_tidb_taskboard_agent"
	agentID := "agt_tidb_taskboard_agent"
	machineID := "mac_tidb_taskboard_agent"
	channelID := "chn_tidb_taskboard"

	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES
    ($1, 'tidb-taskboard@example.com', 'tidb-taskboard-user', 'TiDB Taskboard User', '', FALSE, 'hash', $3, $3, $3),
    ($2, 'tidb-taskboard-reviewer@example.com', 'tidb-taskboard-reviewer', 'TiDB Taskboard Reviewer', '', FALSE, 'hash', $3, $3, $3)
`, userID, reviewerUserID, now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'tidb-taskboard', 'TiDB Taskboard Workspace', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, dataDB, workspaceID, now), now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, status, created_at, updated_at)
VALUES
    ($1, $2, 'human', 'TiDB Taskboard User', $3, 'owner', 'active', $5, $5),
    ($4, $2, 'human', 'TiDB Taskboard Reviewer', $6, 'member', 'active', $5, $5)
`, memberID, workspaceID, userID, reviewerMemberID, now, reviewerUserID)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO members (id, workspace_id, type, display_name, status, created_at, updated_at)
VALUES ($1, $2, 'agent', 'TiDB Taskboard Agent', 'active', $3, $3)
`, agentMemberID, workspaceID, now)
	seedTaskboardChannelScope(t, ctx, dataDB, workspaceID, channelID, "all", "", "", "public", "", now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
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, $5, $5, NULL, NULL), ($1, $3, 'public', NULL, 0, 0, 0, $5, $5, NULL, NULL), ($1, $4, 'public', NULL, 0, 0, 0, $5, $5, NULL, NULL)
`, channelID, memberID, reviewerMemberID, agentMemberID, now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, connected_at, created_at, updated_at)
VALUES ($1, $2, $3, 'tidb-taskboard-machine', 'api_key_tidb_taskboard', $4, $4, $4)
`, machineID, workspaceID, userID, now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
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, $2, $3, $4, 'tidb-taskboard-agent', '', 'codex', 'gpt-5.4', '', '', '', TRUE, 'active', 'online', '', $5, $5)
`, agentID, workspaceID, machineID, agentMemberID, now)

	seedTaskboardChannelScopeFacts(t, ctx, dataDB, workspaceID, now)

	service := New(dataDB)
	createResponse, err := service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Board:   BoardRef{ChannelID: channelID},
		Titles:  []string{"Ship TiDB taskboard regression"},
	})
	require.NoError(t, err)
	require.Len(t, createResponse.Tasks, 1)
	require.Equal(t, int64(1), createResponse.Tasks[0].TaskNumber)
	taskID := createResponse.Tasks[0].ID

	initialTask := createResponse.Tasks[0]
	require.Nil(t, initialTask.AssigneeProfile)
	require.NotNil(t, initialTask.KindField)
	require.True(t, initialTask.KindField.IsNone)
	require.NotNil(t, initialTask.PriorityField)
	require.True(t, initialTask.PriorityField.IsNone)

	subtaskResponse, err := service.CreateSubtasks(ctx, CreateSubtasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Task:    TaskRef{TaskID: taskID},
		Titles:  []string{"TiDB subtask"},
	})
	require.NoError(t, err)
	require.Len(t, subtaskResponse.Tasks, 1)
	subtaskID := subtaskResponse.Tasks[0].ID

	_, err = service.AddTaskCheckpoint(ctx, AddTaskCheckpointRequest{
		Context:    NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Task:       TaskRef{TaskID: taskID},
		Checkpoint: TaskCheckpointInput{Text: "Persist TiDB checkpoint projection"},
	})
	require.NoError(t, err)

	fieldOptions, err := service.ListTaskFieldOptions(ctx, ListTaskFieldOptionsRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
	})
	require.NoError(t, err)
	kindOption := findTaskFieldOption(t, fieldOptions.KindOptions, "bug")
	priorityOption := findTaskFieldOption(t, fieldOptions.PriorityOptions, "p0")

	mustExecTaskboardTiDB(t, ctx, dataDB, `
UPDATE tasks
SET assignee_member_id = $2,
    assigned_at = NOW(),
    claimed_by_member_id = $2,
    claimed_by_ref = $2,
    claimed_at = NOW(),
    kind_option_id = $3,
    priority_option_id = $4,
    discussion_count = 1
WHERE id = $1
`, taskID, agentMemberID, kindOption.ID, priorityOption.ID)

	listResponse, err := service.ListTasks(ctx, ListTasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Board:   BoardRef{ChannelID: channelID},
		Status:  "all",
	})
	require.NoError(t, err)
	require.Len(t, listResponse.Tasks, 1)

	var summary Task
	for _, task := range listResponse.Tasks {
		if task.ID == taskID {
			summary = task
			break
		}
	}
	require.NotEmpty(t, summary.ID)
	require.Equal(t, "Ship TiDB taskboard regression", summary.Title)

	require.Equal(t, kindOption.Label, summary.Kind)
	require.Equal(t, priorityOption.Label, summary.Priority)
	require.Equal(t, int64(1), summary.DiscussionCount)
	require.Equal(t, int64(1), summary.CheckpointProgress.Total)
	require.Equal(t, int64(1), summary.SubtaskProgress.Total)
	require.NotNil(t, summary.AssigneeProfile)
	require.Equal(t, agentID, summary.AssigneeProfile.PrincipalID)

	_, err = service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.ErrorIs(t, err, ErrConflict, "claim must not silently take over another assignee")

	emptyAssignee := ""
	_, err = service.UpdateTask(ctx, UpdateTaskRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Task:    TaskRef{TaskID: taskID},
		Patch:   TaskUpdatePatch{AssigneeRef: &emptyAssignee},
	})
	require.NoError(t, err)

	claimResponse, err := service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Tasks:   []TaskRef{{TaskID: taskID}},
	})
	require.NoError(t, err)
	require.Equal(t, taskID, claimResponse.Task.ID)
	require.Equal(t, "human", claimResponse.Task.AssigneeProfile.ActorType)
	require.Equal(t, userID, claimResponse.Task.AssigneeProfile.PrincipalID)

	updateResponse := moveTaskToDefaultStatusForTest(t, ctx, service, NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"), TaskRef{TaskID: taskID}, StatusInProgress)
	require.True(t, updateResponse.Changed)
	require.Equal(t, string(StatusInProgress), updateResponse.Task.Status)

	_, err = service.ClaimTasks(ctx, ClaimTasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Tasks:   []TaskRef{{TaskID: subtaskID}},
	})
	require.NoError(t, err)
	moveTaskToDefaultStatusForTest(t, ctx, service, NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"), TaskRef{TaskID: subtaskID}, StatusInProgress)
	moveTaskToDefaultStatusForTest(t, ctx, service, NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"), TaskRef{TaskID: subtaskID}, StatusInReview)
	moveTaskToDefaultStatusForTest(t, ctx, service, NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"), TaskRef{TaskID: subtaskID}, StatusDone)

	detailUpdateResponse, err := service.UpdateTask(ctx, UpdateTaskRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Task:    TaskRef{TaskID: taskID},
		Patch: TaskUpdatePatch{
			PinnedMessageRefs: []string{"#all:msg-1"},
			RelevantURLs:      []string{"https://example.com/spec"},
			WorkspaceRefs:     []string{"specs/collaboration/collaboration.md"},
		},
	})
	require.NoError(t, err)
	require.Equal(t, taskID, detailUpdateResponse.Task.ID)

	reviewResponse, err := service.RequestTaskReview(ctx, RequestTaskReviewRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewRequestInput{
			ReviewerRef:         reviewerMemberID,
			Summary:             "Ready for review",
			Questions:           []string{"Does the review payload load correctly?"},
			ReviewTargetType:    "thread_only",
			ReviewTargetPayload: `{"threadRef":"#all:tidb-review"}`,
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInReview), reviewResponse.Task.Status)

	changesResponse, err := service.RequestTaskChanges(ctx, RequestTaskChangesRequest{
		Context: NewUserServiceContext(workspaceID, reviewerUserID, "TiDB Taskboard Reviewer"),
		Task:    TaskRef{TaskID: taskID},
		Review: TaskReviewDecisionInput{
			ReviewID:  reviewResponse.Review.ID,
			Decision:  "Need one more detail update.",
			Summary:   "Not ready yet.",
			Questions: []string{"Can you expand the rollout note?"},
		},
	})
	require.NoError(t, err)
	require.Equal(t, string(StatusInProgress), changesResponse.Task.Status)

	detailResponse, err := service.GetTaskDetail(ctx, GetTaskDetailRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Taskboard User"),
		Task:    TaskRef{TaskID: taskID},
	})
	require.NoError(t, err)

	require.Len(t, detailResponse.Detail.Checkpoints, 1)
	require.Equal(t, "Persist TiDB checkpoint projection", detailResponse.Detail.Checkpoints[0].Text)
	require.Equal(t, []string{"#all:msg-1"}, detailResponse.Detail.PinnedMessageRefs)
	require.Equal(t, []string{"https://example.com/spec"}, detailResponse.Detail.RelevantURLs)
	require.Equal(t, []string{"specs/collaboration/collaboration.md"}, detailResponse.Detail.WorkspaceRefs)
	require.Len(t, detailResponse.Detail.ReviewHistory, 1)
	require.Equal(t, taskReviewStatusChangesRequested, detailResponse.Detail.ReviewHistory[0].Status)
	require.Equal(t, reviewerMemberID, detailResponse.Detail.ReviewHistory[0].ReviewerRef)
}

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

	ctx := context.Background()
	dataDB := startTaskboardTiDBTestDB(t)

	now := time.Date(2026, 4, 24, 1, 0, 0, 0, time.UTC)
	workspaceID := "wsp_tidb_browse"
	userID := "usr_tidb_browse"
	memberID := "mem_tidb_browse"
	channelID := "chn_tidb_browse"

	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'tidb-browse@example.com', 'tidb-browse-user', 'TiDB Browse User', '', FALSE, 'hash', $2, $2, $2)
`, userID, now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'tidb-browse', 'TiDB Browse Workspace', 0, $3, $3)
`, workspaceID, testfixture.SeedWorkspaceOrganization(t, ctx, dataDB, workspaceID, now), now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, status, created_at, updated_at)
VALUES ($1, $2, 'human', 'TiDB Browse User', $3, 'owner', 'active', $4, $4)
`, memberID, workspaceID, userID, now)
	seedTaskboardChannelScope(t, ctx, dataDB, workspaceID, channelID, "all", "", "", "public", "", now)
	mustExecTaskboardTiDB(t, ctx, dataDB, `
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, $3, $3, NULL, NULL)
`, channelID, memberID, now)

	seedTaskboardChannelScopeFacts(t, ctx, dataDB, workspaceID, now)

	service := New(dataDB)
	createResponse, err := service.CreateTasks(ctx, CreateTasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Browse User"),
		Board:   BoardRef{ChannelID: channelID},
		Titles:  []string{"TiDB Visible One", "TiDB Visible Two"},
	})
	require.NoError(t, err)
	require.Len(t, createResponse.Tasks, 2)

	_, err = service.CreateSubtasks(ctx, CreateSubtasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Browse User"),
		Board:   BoardRef{ChannelID: channelID},
		Task:    TaskRef{TaskID: createResponse.Tasks[0].ID},
		Titles:  []string{"TiDB Visible Child"},
	})
	require.NoError(t, err)

	mustExecTaskboardTiDB(t, ctx, dataDB, `
UPDATE tasks
SET blocked_reason = 'TiDB blocker',
    last_activity_at = $2,
    updated_at = $2
WHERE id = $1
`, createResponse.Tasks[1].ID, now.Add(2*time.Hour))
	mustExecTaskboardTiDB(t, ctx, dataDB, `
UPDATE tasks
SET blocked_reason = 'TiDB blocker',
    last_activity_at = $2,
    updated_at = $2
WHERE id = $1
`, createResponse.Tasks[0].ID, now.Add(1*time.Hour))

	page, err := service.BrowseWorkspaceTasks(ctx, BrowseWorkspaceTasksRequest{
		Context: NewUserServiceContext(workspaceID, userID, "TiDB Browse User"),
		Query: TaskBrowseQuery{
			Limit:        1,
			QuickFilters: []string{"blocked"},
			SortMode:     "recently_updated",
		},
	})
	require.NoError(t, err)
	require.Len(t, page.Rows, 1)
	require.Equal(t, int64(2), page.TotalCount)
	require.True(t, page.HasMore)
	require.NotEmpty(t, page.NextCursor)
	require.Equal(t, "TiDB Visible Two", page.Rows[0].Title)
}

func mustExecTaskboardTiDB(t *testing.T, ctx context.Context, db coredb.DB, query string, args ...any) {
	t.Helper()

	_, err := db.Exec(ctx, query, args...)
	require.NoError(t, err)
}
