package runner

import (
	"encoding/json"
	"math/big"
	"regexp"
	"strconv"
	"strings"
)

// jsonNumberLexical is the exact JSON number grammar; it rejects the
// near-misses strconv and big.Rat would otherwise accept ("01", "+1",
// "0x10", "1_0", "1/1").
var jsonNumberLexical = regexp.MustCompile(
	`^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?$`)

const (
	// Integer query values become URL text. Bound both the input work and the
	// normalized output while keeping ample room for exact provider IDs well
	// beyond int64 (including the existing 22-digit contract).
	maxIntegerQueryLexicalBytes      = 256
	maxIntegerQueryExponentMagnitude = 128
	maxIntegerQueryDecimalDigits     = 128
)

// FormatIntegerQueryValue renders one schema-legal JSON Schema integer
// lexical as a plain decimal REST query value. JSON Schema integer also
// accepts exponent and decimal lexicals (1e1, 1.0) that must not reach a
// provider query string verbatim, so every bound number is re-encoded as
// plain decimal digits. The lexical is validated against the JSON number
// grammar first, then the exact big.Rat parse keeps schema-legal integers
// precise up to the explicit query digit/exponent budget. Values are not
// subject to float64 rounding or its safe-integer bound; fractional,
// oversized, or invalid lexicals fail closed as validation errors.
func FormatIntegerQueryValue(value json.Number) (string, error) {
	lexical := value.String()
	if len(lexical) > maxIntegerQueryLexicalBytes ||
		!jsonNumberLexical.MatchString(lexical) ||
		!integerQueryExponentWithinLimit(lexical) {
		return "", Errorf(ErrorValidation, "expected an integer value", nil)
	}
	if exact, err := value.Int64(); err == nil {
		return strconv.FormatInt(exact, 10), nil
	}
	var rational big.Rat
	if _, ok := rational.SetString(lexical); !ok || !rational.IsInt() {
		return "", Errorf(ErrorValidation, "expected an integer value", nil)
	}
	formatted := rational.Num().String()
	if len(strings.TrimPrefix(formatted, "-")) > maxIntegerQueryDecimalDigits {
		return "", Errorf(ErrorValidation, "integer value exceeds query limits", nil)
	}
	return formatted, nil
}

// integerQueryExponentWithinLimit parses only the already grammar-checked,
// bounded exponent token. This rejects resource-amplifying powers before
// big.Rat can allocate 10^N.
func integerQueryExponentWithinLimit(lexical string) bool {
	index := strings.IndexAny(lexical, "eE")
	if index < 0 {
		return true
	}
	exponent, err := strconv.ParseInt(lexical[index+1:], 10, 16)
	return err == nil &&
		exponent >= -maxIntegerQueryExponentMagnitude &&
		exponent <= maxIntegerQueryExponentMagnitude
}

// FormatIntegerQueryValuesInPlace rewrites the schema-legal lexical forms of the
// given optional numbers in place as plain decimal query values; nil and
// empty numbers pass through untouched.
func FormatIntegerQueryValuesInPlace(numbers ...*json.Number) error {
	for _, number := range numbers {
		if number == nil || *number == "" {
			continue
		}
		formatted, err := FormatIntegerQueryValue(*number)
		if err != nil {
			return err
		}
		*number = json.Number(formatted)
	}
	return nil
}
