package run

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

	"github.com/stretchr/testify/require"

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

func TestResourceRejectedCompletionReplayUsesNormalizedNumberDigest(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name   string
		first  json.Number
		replay json.Number
	}{
		{name: "negative zero", first: json.Number("-0"), replay: json.Number("0")},
		{name: "exponent spelling", first: json.Number("1e100"), replay: json.Number("1" + strings.Repeat("0", 100))},
		{name: "finite float64 rounding", first: json.Number("9007199254740993"), replay: json.Number("9007199254740992")},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			kernel := compilePlanKernel(t, fanOutReplayNumberRuntimePlan())
			state := startFanOut(t, kernel, []any{workItemRaw("root", nil, "@dev1")})
			intent, state := stepToMessageIntent(t, kernel, state)
			completion := Completion{
				ActivationID: intent.ActivationID,
				Port:         "completed",
				Output: Values{
					"answer":    strings.Repeat("x", runlimit.MaxRunPayloadBytes),
					"amount":    test.first,
					"workScope": messageScopeObject(*intent.WorkScope),
				},
			}

			rejected, err := kernel.Complete(state, completion, reducerNow)
			require.NoError(t, err)
			activation, err := rejected.State.activation(intent.ActivationID)
			require.NoError(t, err)
			require.Equal(t, ActivationFailed, activation.Status)
			require.NotNil(t, activation.ResourceRejectedCompletion)
			require.Nil(t, activation.Output, "the over-budget output must not enter durable State")

			completion.Output = cloneValues(completion.Output)
			completion.Output["amount"] = test.replay
			replayed, err := kernel.Complete(rejected.State, completion, reducerNow)
			require.NoError(t, err)
			require.Len(t, replayed.Events, 1)
			require.Equal(t, "completion_replayed", replayed.Events[0].Kind)
			require.Equal(t, rejected.State, replayed.State)
		})
	}
}

func TestResourceRejectedCompletionDigestPreservesNonNormalizedNumberHandling(t *testing.T) {
	t.Parallel()
	t.Run("finite-domain overflow remains an item-local resource rejection", func(t *testing.T) {
		kernel := compilePlanKernel(t, fanOutReplayNumberRuntimePlan())
		state := startFanOut(t, kernel, []any{workItemRaw("root", nil, "@dev1")})
		intent, state := stepToMessageIntent(t, kernel, state)
		completion := Completion{
			ActivationID: intent.ActivationID,
			Port:         "completed",
			Output: Values{
				"answer":    "small",
				"amount":    json.Number("1e4095"),
				"workScope": messageScopeObject(*intent.WorkScope),
			},
		}

		rejected, err := kernel.Complete(state, completion, reducerNow)
		require.NoError(t, err, "digest creation must not turn the item-local budget failure into a reducer error")
		activation, err := rejected.State.activation(intent.ActivationID)
		require.NoError(t, err)
		require.Equal(t, ActivationFailed, activation.Status)
		require.NotNil(t, activation.ResourceRejectedCompletion)
		require.Equal(t, workItemResourceLimitFailureCode, activation.Failure.Code)

		replayed, err := kernel.Complete(rejected.State, completion, reducerNow)
		require.NoError(t, err)
		require.Equal(t, "completion_replayed", replayed.Events[0].Kind)

		conflicting := completion
		conflicting.Output = cloneValues(completion.Output)
		conflicting.Output["amount"] = json.Number("10e4094")
		_, err = kernel.Complete(rejected.State, conflicting, reducerNow)
		require.ErrorContains(t, err, "already rejected a different completion result")
	})

	t.Run("invalid and non-finite values retain contract normalization errors", func(t *testing.T) {
		for _, amount := range []any{json.Number("1e"), math.Inf(1), math.NaN()} {
			kernel := compilePlanKernel(t, fanOutReplayNumberRuntimePlan())
			state := startFanOut(t, kernel, []any{workItemRaw("root", nil, "@dev1")})
			intent, state := stepToMessageIntent(t, kernel, state)
			_, err := kernel.Complete(state, Completion{
				ActivationID: intent.ActivationID,
				Port:         "completed",
				Output: Values{
					"answer":    "small",
					"amount":    amount,
					"workScope": messageScopeObject(*intent.WorkScope),
				},
			}, reducerNow)
			require.ErrorContains(t, err, "must be a finite JSON number")
		}
	})
}

func TestResourceRejectedCompletionReceiptDigestsUnboundedFailureText(t *testing.T) {
	t.Parallel()
	kernel := compilePlanKernel(t, fanOutRuntimePlan)
	state := startFanOut(t, kernel, []any{workItemRaw("root", nil, "@dev1")})
	intent, state := stepToMessageIntent(t, kernel, state)
	completion := Completion{
		ActivationID: intent.ActivationID,
		Port:         "completed",
		Output: Values{
			"answer":    strings.Repeat("x", runlimit.MaxRunPayloadBytes),
			"workScope": messageScopeObject(*intent.WorkScope),
		},
		Failure: &Failure{
			Code:    "agent.failed",
			Message: strings.Repeat("m", runlimit.MaxRunStateBytes),
		},
	}

	rejected, err := kernel.Complete(state, completion, reducerNow)
	require.NoError(t, err)
	activation, err := rejected.State.activation(intent.ActivationID)
	require.NoError(t, err)
	require.NotNil(t, activation.ResourceRejectedCompletion)
	require.NotEmpty(t, activation.ResourceRejectedCompletion.FailureDigest)
	require.Equal(t, workItemResourceLimitFailureCode, activation.Failure.Code)
	require.NoError(t, validateFanOutHardStateBudget("large failure replay receipt", rejected.State))

	replayed, err := kernel.Complete(rejected.State, completion, reducerNow)
	require.NoError(t, err)
	require.Equal(t, "completion_replayed", replayed.Events[0].Kind)

	conflicting := completion
	conflicting.Failure = &Failure{Code: completion.Failure.Code, Message: completion.Failure.Message + "x"}
	_, err = kernel.Complete(rejected.State, conflicting, reducerNow)
	require.ErrorContains(t, err, "already rejected a different completion result")
}

func fanOutReplayNumberRuntimePlan() string {
	return strings.Replace(
		fanOutRuntimePlan,
		`      callbackOutput:
        answer: {type: string, required: true}`,
		`      callbackOutput:
        answer: {type: string, required: true}
        amount: {type: number, required: true}`,
		1,
	)
}
