package planmanage

import (
	"encoding/json"
	"errors"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

func TestNormalizeActorKeepsAuthoringProvenanceOnly(t *testing.T) {
	actor, err := normalizeActor(Actor{Type: " human ", ID: " usr_1 ", MemberID: " mem_1 "})
	require.NoError(t, err)
	require.Equal(t, Actor{Type: ActorTypeHuman, ID: "usr_1", MemberID: "mem_1"}, actor)

	_, err = normalizeActor(Actor{Type: "service", ID: "svc_1", MemberID: "mem_1"})
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
}

func TestNormalizePlanKeyRejectsDisplayNamesAndLegacyMetadata(t *testing.T) {
	value, err := normalizePlanKey(" deploy_guard ")
	require.NoError(t, err)
	require.Equal(t, "deploy_guard", value)

	for _, invalid := range []string{"", "Deploy Guard", "1plan", "plan.key"} {
		_, err := normalizePlanKey(invalid)
		require.ErrorIs(t, err, storeerr.ErrBadRequest, invalid)
	}
}

func TestNormalizeLayoutNormalizesObjectAndRejectsOtherJSON(t *testing.T) {
	layout, err := normalizeLayout(json.RawMessage(` { "zoom": 1, "nodes": {} } `))
	require.NoError(t, err)
	require.JSONEq(t, `{"nodes":{},"zoom":1}`, string(layout))

	empty, err := normalizeLayout(nil)
	require.NoError(t, err)
	require.JSONEq(t, `{}`, string(empty))

	for _, invalid := range []json.RawMessage{
		json.RawMessage(`[]`),
		json.RawMessage(`"layout"`),
		json.RawMessage(`{} {}`),
		json.RawMessage(`{`),
	} {
		_, err := normalizeLayout(invalid)
		require.True(t, errors.Is(err, storeerr.ErrBadRequest), string(invalid))
	}
}

func TestNormalizeIdempotencyKeyRequiresVisibleBoundedASCII(t *testing.T) {
	value, err := normalizeIdempotencyKey(" request-1 ")
	require.NoError(t, err)
	require.Equal(t, "request-1", value)

	for _, invalid := range []string{"", "contains space", "换行", string(make([]byte, maxIdempotencyKeyBytes+1))} {
		_, err := normalizeIdempotencyKey(invalid)
		require.ErrorIs(t, err, storeerr.ErrBadRequest)
	}
}

func TestNormalizeListBoundsWork(t *testing.T) {
	normalized, err := normalizeList(ListInput{WorkspaceID: " wsp_1 "})
	require.NoError(t, err)
	require.Equal(t, "wsp_1", normalized.WorkspaceID)
	require.Equal(t, defaultPageLimit, normalized.Limit)

	_, err = normalizeList(ListInput{WorkspaceID: "wsp_1", Limit: maxPageLimit + 1})
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
}

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

	_, err := normalizeSource(strings.Repeat("s", plan.MaxSourceBytes+1))
	require.ErrorIs(t, err, storeerr.ErrBadRequest)

	_, err = normalizeDescription(strings.Repeat("d", MaxDescriptionBytes+1))
	require.ErrorIs(t, err, storeerr.ErrBadRequest)

	_, err = normalizeLayout(json.RawMessage(strings.Repeat(" ", MaxLayoutBytes+1)))
	require.ErrorIs(t, err, storeerr.ErrBadRequest)

	// encoding/json escapes HTML-sensitive characters. Enforce the persisted
	// normalized representation's budget as well as the incoming wire size.
	expandingLayout := json.RawMessage(`{"x":"` + strings.Repeat("&", 50_000) + `"}`)
	require.Less(t, len(expandingLayout), MaxLayoutBytes)
	_, err = normalizeLayout(expandingLayout)
	require.ErrorIs(t, err, storeerr.ErrBadRequest)
}
