package run

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

	"github.com/stretchr/testify/require"

	runstore "github.com/tidbcloud/AgentTeam/internal/store/workflow/run"
	"github.com/tidbcloud/AgentTeam/internal/workflow/runlimit"
)

func TestFanOutJSONCounterMatchesStoreNormalizedNumbers(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		name  string
		value any
	}{
		{name: "positive exponent expands", value: json.Number("1e100")},
		{name: "negative exponent expands", value: json.Number("1e-100")},
		{name: "negative zero", value: json.Number("-0")},
		{name: "float64 exponent", value: float64(1e100)},
		{name: "float32 rounding", value: float32(1.234567)},
		{name: "integer rounds through Store float64 domain", value: int64(9007199254740993)},
	}
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			value := Values{"number": testCase.value}
			counter := fanOutJSONCounter{
				path: "test", maxBytes: runlimit.MaxRunPayloadBytes,
				maxDepth:  runlimit.MaxRunPayloadJSONNesting,
				maxValues: runlimit.MaxRunPayloadJSONValues,
			}
			require.NoError(t, counter.count(value, 0))
			raw, err := json.Marshal(value)
			require.NoError(t, err)
			deterministic, err := runstore.DeterministicPayloadJSON("test", raw, true)
			require.NoError(t, err)
			require.Equal(t, len(raw), counter.rawBytes)
			require.Equal(t, len(deterministic), counter.bytes)
		})
	}

	t.Run("non finite and Store-overflow numbers reject identically", func(t *testing.T) {
		for _, value := range []any{json.Number("1e4095"), math.Inf(1), math.NaN()} {
			counter := fanOutJSONCounter{
				path: "test", maxBytes: runlimit.MaxRunPayloadBytes,
				maxDepth:  runlimit.MaxRunPayloadJSONNesting,
				maxValues: runlimit.MaxRunPayloadJSONValues,
			}
			require.Error(t, counter.count(Values{"number": value}, 0))
			raw, marshalErr := json.Marshal(Values{"number": value})
			if marshalErr == nil {
				_, storeErr := runstore.DeterministicPayloadJSON("test", raw, true)
				require.Error(t, storeErr)
			}
		}
	})
}

func TestFanOutJSONCounterRejectsRawOversizeNormalizedSmallNumber(t *testing.T) {
	t.Parallel()
	value := Values{
		// This is a valid JSON number whose deterministic Store representation is 1,
		// but whose compact raw encoding is larger than one payload budget.
		"number": json.Number("1." + strings.Repeat("0", runlimit.MaxRunPayloadBytes)),
	}
	raw, err := json.Marshal(value)
	require.NoError(t, err)
	require.Greater(t, len(raw), runlimit.MaxRunPayloadBytes)
	normalizedNum, err := runlimit.NormalizedNumberString(value["number"].(json.Number).String())
	require.NoError(t, err)
	require.Equal(t, "1", normalizedNum)

	err = validateFanOutPayloadBudget("test", value)
	require.Error(t, err)
	require.True(t, isFanOutResourceLimit(err))
	require.ErrorContains(t, err, "raw payload byte")

	_, err = runstore.DeterministicPayloadJSON("test", raw, true)
	require.Error(t, err, "the reducer must mirror the Store's raw-input byte gate")
}

func TestFanOutStateStreamingMeasurementMatchesStoreNormalizedState(t *testing.T) {
	t.Parallel()
	state := &State{
		Status:    StatusRunning,
		Input:     Values{"exponent": json.Number("1e100"), "negativeZero": json.Number("-0")},
		Variables: Values{},
		Activations: []Activation{{
			ID: "a1", Sequence: 1, NodeID: "node", InputPort: "in",
			Input: Values{"small": json.Number("1e-100")}, SelectedPorts: []string{},
			Status: ActivationWaiting, Lineage: []ScopeRef{}, OwnedScopeIDs: []string{},
		}},
		Scopes: []Scope{}, NextActivation: 1,
	}
	raw, err := json.Marshal(state)
	require.NoError(t, err)
	measurement, err := measureDeterministicJSONBudget(
		raw,
		runlimit.MaxRunStateBytes,
		runlimit.MaxRunStateJSONNesting,
		runlimit.MaxRunStateJSONValues,
	)
	require.NoError(t, err)
	deterministic, err := runstore.DeterministicStateJSON(raw)
	require.NoError(t, err)
	require.Equal(t, len(deterministic), measurement.bytes)
	require.NoError(t, validateFanOutStateExact("test state", state))
}
