package plan

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"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 TestWorkflowPlanJSONAdapterIsStrictAndBounded(t *testing.T) {
	tests := []struct {
		name       string
		body       string
		wantStatus int
		wantCode   string
		wantCalled bool
	}{
		{name: "valid", body: `{"source":""}`, wantStatus: http.StatusNoContent, wantCalled: true},
		{name: "unknown source typo", body: `{"soruce":"plan"}`, wantStatus: http.StatusBadRequest, wantCode: "plan_invalid_json"},
		{name: "case variant is not the declared field", body: `{"Source":"plan"}`, wantStatus: http.StatusBadRequest, wantCode: "plan_invalid_json"},
		{name: "unknown snake case field", body: `{"source":"plan","make_default":true}`, wantStatus: http.StatusBadRequest, wantCode: "plan_invalid_json"},
		{name: "duplicate key", body: `{"source":"one","source":"two"}`, wantStatus: http.StatusBadRequest, wantCode: "plan_invalid_json"},
		{name: "multiple documents", body: `{"source":"one"} {"source":"two"}`, wantStatus: http.StatusBadRequest, wantCode: "plan_invalid_json"},
		{name: "null is not a request object", body: `null`, wantStatus: http.StatusBadRequest, wantCode: "plan_invalid_json"},
		{name: "missing required source", body: `{}`, wantStatus: http.StatusBadRequest, wantCode: "plan_invalid_request", wantCalled: true},
		{
			name:       "oversized",
			body:       `{"source":"` + strings.Repeat("x", int(maxWorkflowPlanRequestBodyBytes)) + `"}`,
			wantStatus: http.StatusRequestEntityTooLarge,
			wantCode:   "plan_request_too_large",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			called := false
			recorder := httptest.NewRecorder()
			_, router := gin.CreateTestContext(recorder)
			router.POST("/draft",
				func(ctx *gin.Context) {
					api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_1"}})
					api.SetCurrentWorkspace(ctx, workspace.Summary{WorkspaceID: "wsp_1"})
				},
				handleWorkspaceJSON(func(request api.WorkspaceScopedJSON[dto.WorkflowPlanDraftSaveRequest]) (api.Response, error) {
					called = true
					if _, err := requiredSource(request.Body.Source); err != nil {
						return api.Response{}, err
					}
					return api.NoContent(), nil
				}),
			)

			request := httptest.NewRequest(http.MethodPost, "/draft", strings.NewReader(test.body))
			request.Header.Set("Content-Type", "application/json")
			router.ServeHTTP(recorder, request)

			require.Equal(t, test.wantStatus, recorder.Code, recorder.Body.String())
			require.Equal(t, test.wantCalled, called)
			if test.wantCode != "" {
				var response struct {
					Code string `json:"code"`
				}
				require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response))
				require.Equal(t, test.wantCode, response.Code)
			}
		})
	}
}

func TestWorkflowPlanJSONAdapterRejectsExcessiveNesting(t *testing.T) {
	payload := `{"source":"plan","layout":` + strings.Repeat("[", maxWorkflowPlanJSONDepth+1) +
		strings.Repeat("]", maxWorkflowPlanJSONDepth+1) + `}`
	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	ctx.Request = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(payload))
	_, err := decodeWorkflowPlanJSON[dto.WorkflowPlanDraftSaveRequest](ctx)
	require.Error(t, err)

	response, ok := classifyError(err)
	require.True(t, ok)
	require.Equal(t, http.StatusBadRequest, response.Status)
	require.Equal(t, "plan_invalid_json", response.Code)
	require.Equal(t, "plan", response.Stage)
}
