package task

import (
	"context"
	"encoding/base64"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/store/taskboard"
)

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

	now := time.Date(2026, 8, 16, 1, 2, 3, 456789000, time.UTC)
	token := converter.ToTaskSubtasksCursorToken(taskboard.SubtaskPageCursor{CreatedAt: now, ID: "tsk_123"})
	require.NotEmpty(t, token)

	decoded, err := converter.FromTaskSubtasksCursorToken(token)
	require.NoError(t, err)
	require.Equal(t, "tsk_123", decoded.ID)
	require.True(t, decoded.CreatedAt.Equal(now))
}

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

	decoded, err := converter.FromTaskSubtasksCursorToken("")
	require.NoError(t, err)
	require.True(t, decoded.IsZero())
	require.Empty(t, converter.ToTaskSubtasksCursorToken(taskboard.SubtaskPageCursor{}))
}

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

	cases := map[string]string{
		"not base64":        "!!!not-base64!!!",
		"empty id":          base64.RawURLEncoding.EncodeToString([]byte(`{"createdAt":"2026-08-16T01:02:03.456789Z","id":"  "}`)),
		"zero created at":   base64.RawURLEncoding.EncodeToString([]byte(`{"id":"tsk_1"}`)),
		"missing id":        base64.RawURLEncoding.EncodeToString([]byte(`{"createdAt":"2026-08-16T01:02:03.456789Z"}`)),
		"garbage json":      "eyJmb28iOiJiYXIifQ", // {"foo":"bar"}
		"truncated payload": "eyJjcmVhdGVkQXQi",
	}
	for name, token := range cases {
		t.Run(name, func(t *testing.T) {
			_, err := converter.FromTaskSubtasksCursorToken(token)
			require.Error(t, err)
		})
	}
}

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

	handler := &SubtaskHandler{taskHandlerBase: taskHandlerBase{taskBoard: &recordingSubtaskListBoard{}}}
	for _, rawLimit := range []string{"0", "-1", "abc", "1.5", "101"} {
		t.Run("limit="+rawLimit, func(t *testing.T) {
			recorder := httptest.NewRecorder()
			ctx, _ := gin.CreateTestContext(recorder)
			ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/tasks/tsk_1/subtasks?limit="+rawLimit, nil)
			ctx.Params = gin.Params{{Key: "taskId", Value: "tsk_1"}}
			setTaskAuth(ctx)

			serveTaskWorkspace(ctx, handler.List)

			require.Equal(t, http.StatusBadRequest, recorder.Code)
		})
	}
}

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

	handler := &SubtaskHandler{taskHandlerBase: taskHandlerBase{taskBoard: &recordingSubtaskListBoard{}}}
	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/tasks/tsk_1/subtasks?cursor=!!bad!!", nil)
	ctx.Params = gin.Params{{Key: "taskId", Value: "tsk_1"}}
	setTaskAuth(ctx)

	serveTaskWorkspace(ctx, handler.List)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
}

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

	board := &recordingSubtaskListBoard{response: ListTaskSubtasksResponse{}}
	handler := &SubtaskHandler{taskHandlerBase: taskHandlerBase{taskBoard: board}}

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/tasks/tsk_1/subtasks", nil)
	ctx.Params = gin.Params{{Key: "taskId", Value: "tsk_1"}}
	setTaskAuth(ctx)

	serveTaskWorkspace(ctx, handler.List)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Len(t, board.requests, 1)
	require.Equal(t, taskSubtasksPageDefaultLimit, board.requests[0].Limit)
	require.Equal(t, "tsk_1", board.requests[0].Task.TaskID)
	require.True(t, board.requests[0].Cursor.IsZero())
}

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

	now := time.Date(2026, 8, 16, 2, 0, 0, 0, time.UTC)
	cursorToken := converter.ToTaskSubtasksCursorToken(taskboard.SubtaskPageCursor{CreatedAt: now, ID: "tsk_abc"})
	board := &recordingSubtaskListBoard{response: ListTaskSubtasksResponse{}}
	handler := &SubtaskHandler{taskHandlerBase: taskHandlerBase{taskBoard: board}}

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/tasks/tsk_1/subtasks?limit=25&cursor="+cursorToken, nil)
	ctx.Params = gin.Params{{Key: "taskId", Value: "tsk_1"}}
	setTaskAuth(ctx)

	serveTaskWorkspace(ctx, handler.List)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Len(t, board.requests, 1)
	require.Equal(t, 25, board.requests[0].Limit)
	require.Equal(t, "tsk_abc", board.requests[0].Cursor.ID)
	require.True(t, board.requests[0].Cursor.CreatedAt.Equal(now))
}

type recordingSubtaskListBoard struct {
	TaskBoardService
	requests []ListTaskSubtasksRequest
	response ListTaskSubtasksResponse
}

func (board *recordingSubtaskListBoard) ListTaskSubtasks(_ context.Context, request ListTaskSubtasksRequest) (ListTaskSubtasksResponse, error) {
	board.requests = append(board.requests, request)
	return board.response, nil
}
