package task_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	. "github.com/tidbcloud/AgentTeam/internal/api/testkit"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/taskboard"
	"github.com/tidbcloud/AgentTeam/internal/unread"
)

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

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()
	auth, workspaceID, channelID := SetupTaskHTTPActor(t, client, server.URL, "related-thread@example.com", "related-thread")
	memberID := TaskWorkspaceMemberIDForUser(t, runtime, workspaceID, auth.User.ID)

	var parentMessage message.AppMessage
	response := DoJSON(t, client, http.MethodPost, server.URL+WorkspaceScopeMessagesPath(workspaceID, channelID), map[string]any{
		"scopeId": channelID,
		"content": "parent message for related thread tasks",
	}, WorkspaceHeaders(auth.AccessToken, workspaceID), &parentMessage)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var threadSummary message.ReplyThreadSummary
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/channels/"+channelID+"/threads", map[string]any{
		"parentMessageId": parentMessage.ID,
	}, WorkspaceHeaders(auth.AccessToken, workspaceID), &threadSummary)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, threadSummary.ThreadChannelID)

	created, err := runtime.TaskBoard.CreateTasks(runtime.Ctx, taskboard.CreateTasksRequest{
		Context: taskboard.NewUserServiceContext(workspaceID, auth.User.ID, auth.User.Name),
		Board:   taskboard.BoardRef{ChannelID: channelID},
		Titles:  []string{"Related thread root"},
		Metadata: &taskboard.CreateTaskMetadata{
			SourceThreadChannelID:       threadSummary.ThreadChannelID,
			SourceThreadParentChannelID: channelID,
			SourceThreadParentMessageID: parentMessage.ID,
			SourceThreadRef:             "#related-thread",
		},
	})
	require.NoError(t, err)
	require.Len(t, created.Tasks, 1)
	root := created.Tasks[0]

	subtasks, err := runtime.TaskBoard.CreateSubtasks(runtime.Ctx, taskboard.CreateSubtasksRequest{
		Context: taskboard.NewUserServiceContext(workspaceID, auth.User.ID, auth.User.Name),
		Task:    taskboard.TaskRef{TaskID: root.ID},
		Titles:  []string{"Related thread child"},
	})
	require.NoError(t, err)
	require.Len(t, subtasks.Tasks, 1)
	child := subtasks.Tasks[0]

	_, err = runtime.TaskBoard.AddTaskCheckpoint(runtime.Ctx, taskboard.AddTaskCheckpointRequest{
		Context:    taskboard.NewUserServiceContext(workspaceID, auth.User.ID, auth.User.Name),
		Task:       taskboard.TaskRef{TaskID: root.ID},
		Checkpoint: taskboard.TaskCheckpointInput{Text: "checkpoint visible in related task signal"},
	})
	require.NoError(t, err)

	applyManualTaskUnreadForAPITest(t, runtime, workspaceID, memberID, root.TaskScopeID)

	var related dto.RelatedTasksResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/related/thread/"+threadSummary.ThreadChannelID, nil, WorkspaceHeaders(auth.AccessToken, workspaceID), &related)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, related.Tasks, 2)
	require.Equal(t, root.ID, related.Tasks[0].TaskID)
	require.Equal(t, child.ID, related.Tasks[1].TaskID)
	require.NotNil(t, related.Tasks[0].Source)
	require.Equal(t, message.ContributionSourceKindHumanCompose, related.Tasks[0].Source.Kind)
	require.Equal(t, message.ContributionSourceRefTypeMessage, related.Tasks[0].Source.RefType)
	require.Equal(t, parentMessage.ID, related.Tasks[0].Source.RefID)
	require.Equal(t, threadSummary.ThreadChannelID, related.Tasks[0].SourceThread.ThreadChannelID)
	require.Equal(t, "/channels/"+channelID+"/tasks/1", related.Tasks[0].Route)
	require.NotNil(t, related.Tasks[0].LastSignal)
	require.Equal(t, "checkpoint_added", related.Tasks[0].LastSignal.Type)
	require.True(t, related.Tasks[0].Unread.ManualUnread)
	require.False(t, related.HasMore)
	require.Empty(t, related.NextCursor)
}

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

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()
	auth, workspaceID, channelID := SetupTaskHTTPActor(t, client, server.URL, "related-agent@example.com", "related-agent")

	var machineResponse dto.CreateMachineResponse
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/machines", map[string]any{
		"name": "related-agent-machine",
	}, AuthHeaders(auth.AccessToken), &machineResponse)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, machineResponse.Machine.ID)

	var ownedAgent agent.AppAgent
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/agents", map[string]any{
		"name":      "related-agent-worker",
		"runtime":   "codex",
		"machineId": machineResponse.Machine.ID,
	}, WorkspaceHeaders(auth.AccessToken, workspaceID), &ownedAgent)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, ownedAgent.ID)

	var agentMemberID string
	require.NoError(t, runtime.Pool.QueryRow(runtime.Ctx, `SELECT member_id FROM agents WHERE id = $1`, ownedAgent.ID).Scan(&agentMemberID))
	require.NotEmpty(t, agentMemberID)

	current, err := runtime.TaskBoard.CreateTasks(runtime.Ctx, taskboard.CreateTasksRequest{
		Context: taskboard.NewUserServiceContext(workspaceID, auth.User.ID, auth.User.Name),
		Board:   taskboard.BoardRef{ChannelID: channelID},
		Task: &taskboard.TaskCreateInput{
			Title:       "Assigned execution task",
			AssigneeRef: agentMemberID,
		},
	})
	require.NoError(t, err)
	require.Len(t, current.Tasks, 1)

	claimed, err := runtime.TaskBoard.CreateTasks(runtime.Ctx, taskboard.CreateTasksRequest{
		Context: taskboard.NewUserServiceContext(workspaceID, auth.User.ID, auth.User.Name),
		Board:   taskboard.BoardRef{ChannelID: channelID},
		Titles:  []string{"Claimed execution task"},
	})
	require.NoError(t, err)
	require.Len(t, claimed.Tasks, 1)
	_, err = runtime.Pool.Exec(runtime.Ctx, `
UPDATE tasks
SET assignee_member_id = $2,
    assigned_at = CURRENT_TIMESTAMP(6),
    claimed_by_member_id = $2,
    claimed_by_ref = $2,
    claimed_at = CURRENT_TIMESTAMP(6),
    status = 'in_progress'
WHERE id = $1
`, claimed.Tasks[0].ID, agentMemberID)
	require.NoError(t, err)

	suggested, err := runtime.TaskBoard.CreateTasks(runtime.Ctx, taskboard.CreateTasksRequest{
		Context: taskboard.NewUserServiceContext(workspaceID, auth.User.ID, auth.User.Name),
		Board:   taskboard.BoardRef{ChannelID: channelID},
		Task: &taskboard.TaskCreateInput{
			Title: "Unassigned task",
		},
	})
	require.NoError(t, err)
	require.Len(t, suggested.Tasks, 1)

	var related dto.RelatedTasksResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/related/agents/"+ownedAgent.ID+"/execution", nil, WorkspaceHeaders(auth.AccessToken, workspaceID), &related)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, related.Tasks, 2)
	require.Equal(t, current.Tasks[0].ID, related.Tasks[0].TaskID)
	require.Equal(t, claimed.Tasks[0].ID, related.Tasks[1].TaskID)
	require.NotNil(t, related.Tasks[0].AssigneeProfile)
	require.Equal(t, ownedAgent.ID, related.Tasks[0].AssigneeProfile.PrincipalID)
	require.NotNil(t, related.Tasks[1].AssigneeProfile)
	require.Equal(t, ownedAgent.ID, related.Tasks[1].AssigneeProfile.PrincipalID)
	for _, row := range related.Tasks {
		require.NotEqual(t, suggested.Tasks[0].ID, row.TaskID)
	}
	require.False(t, related.HasMore)
	require.Empty(t, related.NextCursor)
}

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

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()
	auth, workspaceID, channelID := SetupTaskHTTPActor(t, client, server.URL, "related-agent-page@example.com", "related-agent-page")
	memberID := TaskWorkspaceMemberIDForUser(t, runtime, workspaceID, auth.User.ID)

	var machineResponse dto.CreateMachineResponse
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/machines", map[string]any{
		"name": "related-agent-page-machine",
	}, AuthHeaders(auth.AccessToken), &machineResponse)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var ownedAgent agent.AppAgent
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/agents", map[string]any{
		"name":      "related-agent-page-worker",
		"runtime":   "codex",
		"machineId": machineResponse.Machine.ID,
	}, WorkspaceHeaders(auth.AccessToken, workspaceID), &ownedAgent)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var agentMemberID string
	require.NoError(t, runtime.Pool.QueryRow(runtime.Ctx, `SELECT member_id FROM agents WHERE id = $1`, ownedAgent.ID).Scan(&agentMemberID))

	created, err := runtime.TaskBoard.CreateTasks(runtime.Ctx, taskboard.CreateTasksRequest{
		Context: taskboard.NewUserServiceContext(workspaceID, auth.User.ID, auth.User.Name),
		Board:   taskboard.BoardRef{ChannelID: channelID},
		Titles:  []string{"Active execution A", "Active execution B", "Done execution"},
	})
	require.NoError(t, err)
	require.Len(t, created.Tasks, 3)
	for _, task := range created.Tasks {
		_, err = runtime.Pool.Exec(runtime.Ctx, `
UPDATE tasks
SET assignee_member_id = $2,
    assigned_at = CURRENT_TIMESTAMP(6),
    claimed_by_member_id = $2,
    claimed_by_ref = $2,
    claimed_at = CURRENT_TIMESTAMP(6)
WHERE id = $1
`, task.ID, agentMemberID)
		require.NoError(t, err)
	}
	_, err = runtime.Pool.Exec(runtime.Ctx, `
UPDATE tasks
SET status = 'done',
    completed_at = created_at
WHERE id = $1
`, created.Tasks[2].ID)
	require.NoError(t, err)
	applyManualTaskUnreadForAPITest(t, runtime, workspaceID, memberID, created.Tasks[0].TaskScopeID)
	applyManualTaskUnreadForAPITest(t, runtime, workspaceID, memberID, created.Tasks[2].TaskScopeID)

	var first dto.RelatedTasksResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/related/agents/"+ownedAgent.ID+"/execution?limit=1", nil, WorkspaceHeaders(auth.AccessToken, workspaceID), &first)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, first.Tasks, 1)
	require.Equal(t, created.Tasks[0].ID, first.Tasks[0].TaskID)
	require.True(t, first.HasMore)
	require.NotEmpty(t, first.NextCursor)

	var second dto.RelatedTasksResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/related/agents/"+ownedAgent.ID+"/execution?limit=1&cursor="+first.NextCursor, nil, WorkspaceHeaders(auth.AccessToken, workspaceID), &second)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, second.Tasks, 1)
	require.Equal(t, created.Tasks[1].ID, second.Tasks[0].TaskID)
	require.False(t, second.HasMore)

	var donePage dto.RelatedTasksResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/related/agents/"+ownedAgent.ID+"/execution?status=done&limit=5", nil, WorkspaceHeaders(auth.AccessToken, workspaceID), &donePage)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, donePage.Tasks, 1)
	require.Equal(t, created.Tasks[2].ID, donePage.Tasks[0].TaskID)
	require.Equal(t, string(taskboard.StatusDone), donePage.Tasks[0].Status)

	var summary dto.RelatedTasksSummaryResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces/"+workspaceID+"/tasks/related/agents/"+ownedAgent.ID+"/execution/summary?limit=1", nil, WorkspaceHeaders(auth.AccessToken, workspaceID), &summary)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Equal(t, int64(3), summary.TaskCount, "summary is full-count across all related tasks")
	require.Equal(t, int64(1), summary.StatusCounts[string(taskboard.StatusDone)], "summary statusCounts includes done tasks")
	require.False(t, summary.Unread.ManualUnread, "full-count summary does not carry runtime unread")
}

func applyManualTaskUnreadForAPITest(t *testing.T, runtime Runtime, workspaceID string, memberID string, taskScopeID string) {
	t.Helper()

	require.NotNil(t, runtime.UnreadRuntime)
	require.NoError(t, runtime.UnreadRuntime.ApplyManualUnread(unread.ManualUnreadChanged{
		WorkspaceID:  workspaceID,
		MemberID:     memberID,
		ScopeID:      taskScopeID,
		ManualUnread: true,
	}))
}
