package task

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

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

	for _, status := range []string{"open", "claimed", "completed", "non_done", "paused"} {
		recorder := httptest.NewRecorder()
		ctx, _ := gin.CreateTestContext(recorder)
		ctx.Request = httptest.NewRequest(
			http.MethodGet,
			"/api/v1/workspaces/wsp_1/channels/ch_1/tasks?status="+status,
			nil,
		)
		ctx.Params = gin.Params{{Key: "channelId", Value: "ch_1"}}
		setTaskAuth(ctx)

		serveTaskWorkspace(
			ctx,
			newRouteHandlers(Dependencies{}).collection.ResourceList,
		)

		require.Equal(t, http.StatusBadRequest, recorder.Code)
		require.Equal(
			t,
			"status must be one of active, all, todo, in_progress, in_review, or done",
			jsonError(t, recorder),
		)
	}
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/channels/ch_1/tasks/0", nil)
	ctx.Params = gin.Params{
		{Key: "channelId", Value: "ch_1"},
		{Key: "taskNumber", Value: "0"},
	}
	setTaskAuth(ctx)

	serveTaskWorkspace(ctx, newRouteHandlers(Dependencies{}).detail.GetByNumber)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Equal(t, "taskNumber must be a positive integer", jsonError(t, recorder))
}

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

	includeTimeline, err := taskDetailIncludeTimeline("")
	require.NoError(t, err)
	require.True(t, includeTimeline)

	includeTimeline, err = taskDetailIncludeTimeline("false")
	require.NoError(t, err)
	require.False(t, includeTimeline)
}

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

	_, err := taskDetailIncludeTimeline("timeline")
	require.Error(t, err)
}

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

	board := &fakeDetailTaskBoard{detailResponse: GetTaskDetailResponse{
		Detail: TaskDetail{Task: Task{ID: "tsk_1", ChannelID: "ch_1", TaskNumber: 1}},
	}}
	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/channels/ch_1/tasks/1?includeTimeline=false", nil)
	ctx.Params = gin.Params{{Key: "channelId", Value: "ch_1"}, {Key: "taskNumber", Value: "1"}}
	setTaskAuth(ctx)

	handler := &DetailHandler{taskHandlerBase: taskHandlerBase{taskBoard: board}}
	serveTaskWorkspace(ctx, handler.GetByNumber)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Len(t, board.detailRequests, 1)
	require.True(t, board.detailRequests[0].SkipTimeline)
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/tasks/workspace/browse", bytes.NewBufferString("{"))
	ctx.Request.Header.Set("Content-Type", "application/json")
	setTaskAuth(ctx)

	serveTaskWorkspaceJSON(ctx, newRouteHandlers(Dependencies{}).browse.WorkspaceTasks)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Equal(t, "Invalid request body", jsonError(t, recorder))
}

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

	payload := dto.TaskResponseFromTask(dto.Task{ID: "tsk_1", Title: "ship task API"})
	require.Equal(t, "tsk_1", payload.Task.ID)
	require.Nil(t, payload.Task.AssigneeProfile)
}

func setTaskAuth(ctx *gin.Context) {
	api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "user_1", Name: "alice"}})
	api.SetCurrentWorkspace(ctx, workspace.Summary{WorkspaceID: "workspace_1", MemberID: "member_1"})
}

func serveTaskWorkspace(ctx *gin.Context, handler api.WorkspaceScopedHandler) {
	handleTaskWorkspace(handler)(ctx)
}

func serveTaskWorkspaceJSON[T any](
	ctx *gin.Context,
	handler func(api.WorkspaceScopedJSON[T]) (api.Response, error),
) {
	handleTaskWorkspaceJSON[T](handler)(ctx)
}

func jsonError(t *testing.T, recorder *httptest.ResponseRecorder) string {
	t.Helper()
	var payload struct {
		Error string `json:"error"`
	}
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &payload))
	return payload.Error
}
