package invocation

import (
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

func TestDeterministicJSONObjectDeterministicAndNumeric(t *testing.T) {
	left, err := DeterministicJSONObject("input", []byte(`{"z":1.0,"a":{"n":1e0}}`), MaxInputBytes)
	require.NoError(t, err)
	right, err := DeterministicJSONObject("input", []byte(`{"a":{"n":1},"z":1}`), MaxInputBytes)
	require.NoError(t, err)
	require.Equal(t, `{"a":{"n":1},"z":1}`, string(left))
	require.Equal(t, left, right)
}

func TestDeterministicJSONObjectRejectsUnsafeShapes(t *testing.T) {
	_, err := DeterministicJSONObject("input", []byte(`{"a":1,"a":2}`), MaxInputBytes)
	require.ErrorContains(t, err, "duplicate")
	_, err = DeterministicJSONObject("input", []byte(`{} {}`), MaxInputBytes)
	require.ErrorContains(t, err, "exactly one")
	_, err = DeterministicJSONObject("input", []byte(strings.Repeat(" ", MaxInputBytes+1)), MaxInputBytes)
	require.ErrorContains(t, err, "exceeds")
	deep := strings.Repeat(`{"x":`, maxJSONNesting+2) + `0` + strings.Repeat(`}`, maxJSONNesting+2)
	_, err = DeterministicJSONObject("input", []byte(deep), MaxInputBytes)
	require.ErrorContains(t, err, "nesting")
	wide := `{"x":[` + strings.Repeat(`0,`, maxJSONValues) + `0]}`
	_, err = DeterministicJSONObject("input", []byte(wide), MaxInputBytes)
	require.ErrorContains(t, err, "value count")
	_, err = DeterministicJSONObject("input", []byte(`{"n":1e4095}`), MaxInputBytes)
	require.ErrorContains(t, err, "finite runtime number domain")
}

func TestDeterministicJSONObjectUsesRuntimeNumberSemanticsBeforeBudgeting(t *testing.T) {
	payload, err := DeterministicJSONObject("input", []byte(`{
		"integer":9007199254740993,
		"large":123456789012345678901
	}`), MaxInputBytes)
	require.NoError(t, err)
	require.Equal(t, `{"integer":9007199254740992,"large":123456789012345680000}`, string(payload))

	_, err = DeterministicJSONObject("input", []byte(`{"n":1e100}`), 32)
	require.ErrorContains(t, err, "canonical input exceeds 32 bytes")
}

func TestDeterministicJSONObjectSharesRunLimitNumberAndStructureSemantics(t *testing.T) {
	require.Equal(t, runlimit.MaxRunPayloadJSONNesting, maxJSONNesting)
	require.Equal(t, runlimit.MaxRunPayloadJSONValues, maxJSONValues)

	large, err := runlimit.NormalizedNumberString("1e100")
	require.NoError(t, err)
	payload, err := DeterministicJSONObject(
		"input",
		[]byte(`{"negativeZero":-0,"large":1e100}`),
		runlimit.MaxRunPayloadBytes,
	)
	require.NoError(t, err)
	require.Equal(t, `{"large":`+large+`,"negativeZero":0}`, string(payload))

	_, err = DeterministicJSONObject(
		"input",
		[]byte(`{"number":1e4095}`),
		runlimit.MaxRunPayloadBytes,
	)
	require.ErrorContains(t, err, "finite runtime number domain")
}

func TestDeterministicSourceProvenanceWrapsMaximumLegalInputBudget(t *testing.T) {
	elementCount := maxJSONValues - 2 // input root object + its array
	values := strings.TrimSuffix(strings.Repeat("0,", elementCount), ",")
	input := []byte(`{"items":[` + values + `]}`)
	deterministicInput, err := DeterministicJSONObject("input", input, MaxInputBytes)
	require.NoError(t, err)

	envelope := []byte(`{"source":{"requestInput":` + string(deterministicInput) + `},"binding":{"config":{}}}`)
	_, err = DeterministicJSONObject("source provenance", envelope, MaxSourceProvenanceBytes)
	require.ErrorContains(t, err, "value count")
	envelope2, err := DeterministicSourceProvenanceJSONObject(
		"source provenance", envelope, MaxSourceProvenanceBytes,
	)
	require.NoError(t, err)
	require.JSONEq(t, string(envelope), string(envelope2))
}

func TestOccurrenceRefsAreStableAndSelfDescribing(t *testing.T) {
	actor := Actor{Type: ActorTypeHuman, ID: "usr_1", MemberID: "mem_1"}
	ref := ManualOccurrenceRef("wbs_1", actor, "key")
	require.Equal(t, ref, ManualOccurrenceRef("wbs_1", actor, "key"))
	require.NotEqual(t, ref, ManualOccurrenceRef("wbs_2", actor, "key"))

	instant := time.Date(2026, 7, 21, 1, 2, 3, 123456789, time.FixedZone("offset", 8*60*60))
	require.Equal(t, "schedule:wbs_1:2026-07-20T17:02:03.123456Z", ScheduleOccurrenceRef("wbs_1", instant))
}
