package formula

import (
	"math/rand"
	"strings"
	"testing"
)

// TestFormulaWalkFinding_RecordArityIfRegistered_UsesPredicateAfterBounds pins
// recordArityIfRegistered's check order against a real registered function with
// both bounds and a predicate (CASE: MinArgs 4, unbounded, even-arity). A
// below-minimum count must be rejected by bounds with reason == nil; an
// above-minimum-but-odd count must be rejected by the predicate with a reason;
// a valid even count must be accepted. This exercises the same ordering the
// former synthetic-rule test did, but through the spec registry rather than by
// mutating a shared catalog map.
func TestFormulaWalkFinding_RecordArityIfRegistered_UsesPredicateAfterBounds(t *testing.T) {
	const testFn = "CASE"

	t.Run("below min skips predicate", func(t *testing.T) {
		finding := &formulaWalkFinding{}
		finding.recordArityIfRegistered(testFn, 3) // < MinArgs(4)
		if len(finding.arityMismatches) != 1 {
			t.Fatalf("arity mismatches = %d; want 1", len(finding.arityMismatches))
		}
		if got := finding.arityMismatches[0].reason; got != nil {
			t.Fatalf("predicate reason = %v; want nil when bounds reject before predicate", got)
		}
	})

	t.Run("predicate rejection records reason", func(t *testing.T) {
		finding := &formulaWalkFinding{}
		finding.recordArityIfRegistered(testFn, 5) // >= MinArgs but odd
		if len(finding.arityMismatches) != 1 {
			t.Fatalf("arity mismatches = %d; want 1", len(finding.arityMismatches))
		}
		if got := finding.arityMismatches[0].reason; got == nil || !strings.Contains(got.Error(), "even number of arguments") {
			t.Fatalf("predicate reason = %v; want even-arity predicate error", got)
		}
	})

	t.Run("in-range count accepted", func(t *testing.T) {
		finding := &formulaWalkFinding{}
		finding.recordArityIfRegistered(testFn, 4) // >= MinArgs and even
		if len(finding.arityMismatches) != 0 {
			t.Fatalf("arity mismatches = %d; want 0", len(finding.arityMismatches))
		}
	})
}

func TestRandomValidArgCount_NonCaseRuntimeSignaturesRemainValid(t *testing.T) {
	rng := rand.New(rand.NewSource(20260508))

	names := runtimeFunctionRegistry.Names()
	validated := 0
	for _, fn := range names {
		if fn == "CASE" {
			continue
		}
		rule, _ := runtimeFunctionRegistry.Lookup(fn)
		for i := 0; i < 16; i++ {
			got := randomValidArgCount(rng, rule)
			if got < rule.MinArgs {
				t.Fatalf("%s generated %d args; min is %d", fn, got, rule.MinArgs)
			}
			if rule.MaxArgs != -1 && got > rule.MaxArgs {
				t.Fatalf("%s generated %d args; max is %d", fn, got, rule.MaxArgs)
			}
			if rule.Predicate != nil {
				if err := rule.Predicate(got); err != nil {
					t.Fatalf("%s generated %d args rejected by predicate: %v", fn, got, err)
				}
			}
		}
		validated++
	}

	want := runtimeFunctionRegistry.Len() - 1 // every current entry except CASE
	if validated != want {
		t.Fatalf("validated %d non-CASE runtime signatures; want %d", validated, want)
	}
}
