package run

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/workflow/runlimit"
)

func TestRunStoreLimitsAliasSharedRunLimits(t *testing.T) {
	require.Equal(t, runlimit.MaxRunPayloadBytes, MaxRunPayloadBytes)
	require.Equal(t, runlimit.MaxRunStateBytes, MaxRunStateBytes)
	require.Equal(t, runlimit.MaxRunStateJSONNesting, MaxRunStateJSONNesting)
	require.Equal(t, runlimit.MaxRunStateJSONValues, MaxRunStateJSONValues)
}

func TestDeterministicRunJSONMatchesSharedNumberSemantics(t *testing.T) {
	large, err := runlimit.NormalizedNumberString("1e100")
	require.NoError(t, err)
	wire := []byte(`{"negativeZero":-0,"large":1e100}`)
	want := `{"large":` + large + `,"negativeZero":0}`

	payload, err := DeterministicPayloadJSON("workflow Run output", wire, true)
	require.NoError(t, err)
	require.Equal(t, want, string(payload))
	state, err := DeterministicStateJSON(wire)
	require.NoError(t, err)
	require.Equal(t, want, string(state))

	_, err = DeterministicPayloadJSON("workflow Run output", []byte(`{"number":1e4095}`), true)
	require.ErrorContains(t, err, "finite runtime number domain")
	_, err = DeterministicStateJSON([]byte(`{"number":1e4095}`))
	require.ErrorContains(t, err, "finite runtime number domain")
}

func TestDeterministicRunJSONUsesSharedNestingLimits(t *testing.T) {
	payloadAtLimit := nestedRunJSONObject(runlimit.MaxRunPayloadJSONNesting)
	_, err := DeterministicPayloadJSON("workflow Run output", payloadAtLimit, true)
	require.NoError(t, err)
	_, err = DeterministicPayloadJSON(
		"workflow Run output",
		nestedRunJSONObject(runlimit.MaxRunPayloadJSONNesting+1),
		true,
	)
	require.ErrorContains(t, err, "JSON nesting exceeds")

	stateAtLimit := nestedRunJSONObject(runlimit.MaxRunStateJSONNesting)
	_, err = DeterministicStateJSON(stateAtLimit)
	require.NoError(t, err)
	_, err = DeterministicStateJSON(nestedRunJSONObject(runlimit.MaxRunStateJSONNesting + 1))
	require.ErrorContains(t, err, "JSON nesting exceeds")
}

func TestDeterministicRunJSONUsesSharedValueLimits(t *testing.T) {
	_, err := DeterministicPayloadJSON(
		"workflow Run output",
		flatRunJSONObject(runlimit.MaxRunPayloadJSONValues),
		true,
	)
	require.NoError(t, err)
	_, err = DeterministicPayloadJSON(
		"workflow Run output",
		flatRunJSONObject(runlimit.MaxRunPayloadJSONValues+1),
		true,
	)
	require.ErrorContains(t, err, "JSON value count exceeds")

	_, err = DeterministicStateJSON(flatRunJSONObject(runlimit.MaxRunStateJSONValues))
	require.NoError(t, err)
	_, err = DeterministicStateJSON(flatRunJSONObject(runlimit.MaxRunStateJSONValues + 1))
	require.ErrorContains(t, err, "JSON value count exceeds")
}

func nestedRunJSONObject(scalarDepth int) []byte {
	return []byte(strings.Repeat(`{"value":`, scalarDepth) + `0` + strings.Repeat(`}`, scalarDepth))
}

func flatRunJSONObject(totalValues int) []byte {
	elements := totalValues - 2 // root object and its array are values too.
	return []byte(`{"values":[` + strings.TrimSuffix(strings.Repeat("0,", elements), ",") + `]}`)
}

func TestDeterministicRunStateUsesDedicatedAggregateLimit(t *testing.T) {
	within := []byte(`{"payload":"` + strings.Repeat("x", maxRuntimeJSON) + `"}`)
	canonical, err := deterministicRunState(within)
	require.NoError(t, err, "aggregate state must not inherit the 4 MiB single-payload limit")
	require.NotEmpty(t, canonical)

	// A Run snapshot contains many independently bounded activation/scope
	// envelopes, so the ordinary 8192-value caller-document budget must not be
	// inherited either.
	wideValues := make([]int, 9_000)
	wide, err := json.Marshal(map[string]any{"values": wideValues})
	require.NoError(t, err)
	require.Less(t, len(wide), MaxRunPayloadBytes)
	canonical, err = deterministicRunState(wide)
	require.NoError(t, err)
	require.NotEmpty(t, canonical)

	over := []byte(`{"payload":"` + strings.Repeat("x", MaxRunStateBytes) + `"}`)
	_, err = deterministicRunState(over)
	require.Error(t, err)
}

func TestCancelStateSnapshotFallsBackWhenTerminalStatusCrossesStateLimit(t *testing.T) {
	state := map[string]any{
		"status": RunStatusWaiting, "input": map[string]any{"seed": "start"}, "variables": map[string]any{},
		"activations": []any{}, "scopes": []any{}, "nextActivation": 0, "nextScope": 0, "payload": "",
	}
	empty, err := json.Marshal(state)
	require.NoError(t, err)
	state["payload"] = strings.Repeat("x", MaxRunStateBytes-len(empty))
	nearLimit, err := json.Marshal(state)
	require.NoError(t, err)
	require.Len(t, nearLimit, MaxRunStateBytes)
	_, err = deterministicRunState(nearLimit)
	require.NoError(t, err)

	canceled, prior, err := cancelStateSnapshot(nearLimit)
	require.NoError(t, err)
	require.Equal(t, RunStatusWaiting, prior)
	require.Less(t, len(canceled), MaxRunStateBytes)
	var bounded map[string]any
	require.NoError(t, json.Unmarshal(canceled, &bounded))
	require.Equal(t, RunStatusCanceled, bounded["status"])
	require.Equal(t, "start", bounded["input"].(map[string]any)["seed"])
}
