package agent

import (
	"errors"
	"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/service/channeladmin"
	storeagent "github.com/tidbcloud/AgentTeam/internal/store/agent"
)

// TestClassifyChannelErrorUnknownIsSecretFree proves the closed whitelist: an
// unknown error carrying internal secrets is reduced to a fixed
// 500/channel_internal/channel internal error response. The cause is retained on
// the HTTPError for server logs but must never appear in the wire-facing message
// or code.
func TestClassifyChannelErrorUnknownIsSecretFree(t *testing.T) {
	t.Parallel()
	secret := errors.New("DRIVER_PASSWORD=hunter2 connection refused at 10.0.0.1:3306")
	_, err := classifyChannelError(secret)
	require.Error(t, err)
	var httpErr api.HTTPError
	require.True(t, errors.As(err, &httpErr), "unknown error must be wrapped in api.HTTPError")
	require.Equal(t, http.StatusInternalServerError, httpErr.Status)
	require.Equal(t, "channel internal error", httpErr.Message)
	require.Equal(t, "channel_internal", httpErr.Code)
	require.Contains(t, httpErr.Cause.Error(), "DRIVER_PASSWORD", "cause retained for logs")
	require.NotContains(t, httpErr.Message, "DRIVER_PASSWORD", "wire message must not leak the cause")
	require.NotContains(t, httpErr.Code, "hunter2")
}

// TestClassifyChannelErrorKnownSentinelsHaveStableCodes verifies each service
// sentinel maps to its fixed status + code (the closed whitelist).
func TestClassifyChannelErrorKnownSentinelsHaveStableCodes(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name   string
		err    error
		status int
		code   string
	}{
		{"validation", channeladmin.ErrValidation, http.StatusBadRequest, "channel_invalid_request"},
		{"not_found", channeladmin.ErrNotFound, http.StatusNotFound, "channel_not_found"},
		{"forbidden", channeladmin.ErrForbidden, http.StatusForbidden, "channel_forbidden"},
		{"generic_conflict", channeladmin.ErrConflict, http.StatusConflict, "channel_conflict"},
		{"slug_conflict", channeladmin.ErrSlugConflict, http.StatusConflict, "channel_slug_conflict"},
		{"quota_exceeded", channeladmin.ErrQuotaExceeded, http.StatusForbidden, "quota_exceeded"},
		{"version_conflict", channeladmin.ErrVersionConflict, http.StatusConflict, "channel_version_conflict"},
		{"idempotency_conflict", channeladmin.ErrRequestIdConflict, http.StatusConflict, "channel_idempotency_conflict"},
		{"agent_owner_missing", channeladmin.ErrAgentOwnerMissing, http.StatusForbidden, "channel_forbidden"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			_, err := classifyChannelError(tc.err)
			require.Error(t, err)
			var httpErr api.HTTPError
			require.True(t, errors.As(err, &httpErr))
			require.Equal(t, tc.status, httpErr.Status)
			require.Equal(t, tc.code, httpErr.Code)
		})
	}
}

func TestParseAgentChannelListPage(t *testing.T) {
	t.Parallel()
	page, err := parseAgentChannelListPage("25", " 50 ")
	require.NoError(t, err)
	require.Equal(t, channeladmin.PageInput{Limit: 25, Cursor: "50"}, page)

	for _, rawLimit := range []string{"nope", "0", "101"} {
		_, err := parseAgentChannelListPage(rawLimit, "")
		require.Error(t, err, rawLimit)
		var httpErr api.HTTPError
		require.True(t, errors.As(err, &httpErr))
		require.Equal(t, http.StatusBadRequest, httpErr.Status)
		require.Equal(t, "channel_invalid_request", httpErr.Code)
	}
}

func TestChannelMutationBodyLimitRejectsOversizedBodyBeforeHandler(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.POST(
		"/channels/create",
		func(ctx *gin.Context) {
			api.SetAuthenticatedAgent(ctx, storeagent.AuthenticatedAgent{
				WorkspaceID: "ws", AgentID: "agt_00000000000001", MemberID: "000000000001",
			})
		},
		channelMutationBodyLimit,
		api.HandleAgentScopedJSON[agentChannelCreateRequest](
			func(api.AgentScopedJSON[agentChannelCreateRequest]) (api.Response, error) {
				t.Fatal("oversized request must fail during JSON decoding")
				return api.Response{}, nil
			},
			api.WithInvalidJSONMessage("invalid request body"),
			api.WithStrictJSON(),
		),
	)

	payload := `{"requestId":"req-1","displayName":"` + strings.Repeat("x", (1<<20)+1) + `"}`
	request := httptest.NewRequest(http.MethodPost, "/channels/create", strings.NewReader(payload))
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusRequestEntityTooLarge, response.Code)
	require.JSONEq(t, `{"error":"invalid request body"}`, response.Body.String())
}
