package metadata

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"testing"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/kernel/pkg/formula/spec"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/kernel/pkg/schemautil"
	"github.com/nexuscrm/kernel/pkg/utils"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestValidateRecordReturnsEveryTriggeredValidationRuleWithItsDisplayField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "IQM_Completed__c", Type: constants.FieldTypeBoolean},
			{APIName: "Existing_Database__c", Type: constants.FieldTypeString},
			{APIName: "IQM_Completed_Date__c", Type: constants.FieldTypeDate},
		},
	}
	existingDatabase := "Existing_Database__c"
	completedDate := "IQM_Completed_Date__c"
	rules := []*models.ValidationRule{
		{
			Name:              "IQM_Existing_Database",
			Active:            true,
			Condition:         "AND(IQM_Completed__c, ISBLANK(Existing_Database__c))",
			ErrorMessage:      "Existing Database is required",
			ErrorDisplayField: &existingDatabase,
		},
		{
			Name:              "IQM_Completed_Date",
			Active:            true,
			Condition:         "AND(IQM_Completed__c, ISBLANK(IQM_Completed_Date__c))",
			ErrorMessage:      "IQM Completed Date is required",
			ErrorDisplayField: &completedDate,
		},
	}

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{"IQM_Completed__c": true},
		schema,
		rules,
		nil,
		nil,
	)
	failures, ok := apperrors.AsValidationRuleFailures(err)
	if !ok {
		t.Fatalf("ValidateRecord() error = %T %v, want validation-rule failures", err, err)
	}
	want := []apperrors.ValidationRuleFailure{
		{RuleName: "IQM_Existing_Database", Message: "Existing Database is required", Field: "Existing_Database__c"},
		{RuleName: "IQM_Completed_Date", Message: "IQM Completed Date is required", Field: "IQM_Completed_Date__c"},
	}
	if !reflect.DeepEqual(failures, want) {
		t.Fatalf("ValidateRecord() failures = %#v, want %#v", failures, want)
	}
}

func TestValidateRecordOrdersCollectedFailuresByRuleID(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetCollectAllValidationRules(true)
	rules := []*models.ValidationRule{
		{ID: "rule-b", Name: "Second", Active: true, Condition: "true", ErrorMessage: "second"},
		{ID: "rule-a", Name: "First", Active: true, Condition: "true", ErrorMessage: "first"},
	}

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{},
		&models.ObjectMetadata{APIName: "Lead"},
		rules,
		nil,
		nil,
	)
	failures, ok := apperrors.AsValidationRuleFailures(err)
	if !ok {
		t.Fatalf("ValidateRecord() error = %T %v, want validation-rule failures", err, err)
	}
	want := []apperrors.ValidationRuleFailure{
		{RuleName: "First", Message: "first"},
		{RuleName: "Second", Message: "second"},
	}
	if !reflect.DeepEqual(failures, want) {
		t.Fatalf("ValidateRecord() failures = %#v, want stable rule-ID order %#v", failures, want)
	}
	if rules[0].ID != "rule-b" || rules[1].ID != "rule-a" {
		t.Fatalf("ValidateRecord() mutated caller rule order: %#v", rules)
	}
}

func TestValidateRecordCanOptOutOfCollectAllForCompatibility(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetCollectAllValidationRules(false)
	schema := &models.ObjectMetadata{APIName: "Lead"}
	rules := []*models.ValidationRule{
		{Name: "First", Active: true, Condition: "true", ErrorMessage: "first"},
		{Name: "Second", Active: true, Condition: "true", ErrorMessage: "second"},
	}

	err := vs.ValidateRecord(context.Background(), models.SObject{}, schema, rules, nil, nil)
	failures, ok := apperrors.AsValidationRuleFailures(err)
	if !ok || len(failures) != 1 || failures[0].RuleName != "First" {
		t.Fatalf("ValidateRecord() failures = %#v, want only the first failure after an explicit compatibility opt-out", failures)
	}
}

func TestValidateRecordCollectsStaticFieldsBeforeValidationRules(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetCollectAllValidationRules(true)
	ruleField := "Industry"
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "LastName", Type: constants.FieldTypeString, Required: true},
			{APIName: "Company", Type: constants.FieldTypeString, Required: true},
			{APIName: "Industry", Type: constants.FieldTypeString},
		},
	}
	rules := []*models.ValidationRule{
		{
			ID:                "rule-industry",
			Name:              "Industry_Required",
			Active:            true,
			Condition:         "ISBLANK(Industry)",
			ErrorMessage:      "Industry is required",
			ErrorDisplayField: &ruleField,
		},
	}

	err := vs.ValidateRecord(context.Background(), models.SObject{}, schema, rules, nil, nil)
	leaves, ok := apperrors.AsClientValidationErrors(err)
	if !ok || len(leaves) != 3 {
		t.Fatalf("ValidateRecord() leaves = %#v, %t; want two static failures and one rule", leaves, ok)
	}
	for i, want := range []string{"LastName", "Company"} {
		fieldErr, ok := leaves[i].(*apperrors.ValidationError)
		if !ok || fieldErr.Field != want || fieldErr.Message != "is required" {
			t.Fatalf("static leaf %d = %#v, want required failure for %s", i, leaves[i], want)
		}
	}
	ruleErr, ok := leaves[2].(*apperrors.ValidationRuleError)
	if !ok || ruleErr.RuleName != "Industry_Required" || ruleErr.Field != "Industry" {
		t.Fatalf("rule leaf = %#v, want Industry_Required on Industry", leaves[2])
	}
}

func TestValidateRecordStaticConstraintsCollectEveryFailureByDefault(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "LastName", Type: constants.FieldTypeString, Required: true},
			{APIName: "Company", Type: constants.FieldTypeString, Required: true},
		},
	}

	err := vs.ValidateRecord(context.Background(), models.SObject{}, schema, nil, nil, nil)
	leaves, ok := apperrors.AsClientValidationErrors(err)
	if !ok || len(leaves) != 2 {
		t.Fatalf("ValidateRecord() leaves = %#v, %t; want both required-field errors", leaves, ok)
	}
	for i, want := range []string{"LastName", "Company"} {
		fieldErr, ok := leaves[i].(*apperrors.ValidationError)
		if !ok || fieldErr.Field != want {
			t.Fatalf("leaf %d = %#v, want required-field error for %s", i, leaves[i], want)
		}
	}
}

func TestCollectAllStaticValidationGateRejectsTerminalAndMixedErrors(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetCollectAllValidationRules(true)

	clientErr := apperrors.NewClientValidationErrors(
		apperrors.NewValidationError("LastName", "is required"),
		apperrors.NewValidationError("Company", "is required"),
	)
	if !vs.canCollectClientValidationError(clientErr) {
		t.Fatal("client-only static validation tree was not collectible")
	}

	internalErr := apperrors.NewInternalError("static validation authority failed", fmt.Errorf("catalog unavailable"))
	if vs.canCollectClientValidationError(internalErr) {
		t.Fatal("internal static validation error was collectible")
	}
	if vs.canCollectClientValidationError(errors.Join(clientErr, fmt.Errorf("metadata read failed"))) {
		t.Fatal("mixed static validation tree was collectible")
	}
}

func newValidationServiceWithIncludes(t *testing.T) *ValidationService {
	t.Helper()
	engine := formula.NewEngine(formula.WithoutRuntimeFunctionBundles())
	bundle, err := formula.ParseFunctionBundlePayload([]byte(`{
		"functions": [
			{
				"name": "INCLUDES",
				"minArgs": 2,
				"maxArgs": 2,
				"return": { "kind": "fixed", "type": "boolean" },
				"args": [
					{ "type": "string", "nullable": true },
					{ "type": "string", "nullable": true }
				],
				"evaluator": {
					"kind": "formula_expr_v1",
					"params": ["field", "value"],
					"body": "INCLUDES(field, value)"
				}
			}
		]
	}`))
	if err != nil {
		t.Fatalf("ParseFunctionBundlePayload INCLUDES: %v", err)
	}
	if err := engine.RegisterFunctionBundle(bundle); err != nil {
		t.Fatalf("RegisterFunctionBundle INCLUDES: %v", err)
	}
	return NewValidationService(engine)
}

func TestRewriteValidationFormulaExpressionOnlyWrapsRelationComparisons(t *testing.T) {
	expr := `Contract.StartDate > EffectiveDate && Amount > 0`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if !strings.Contains(rewritten, "STRICTSAFEGT(Contract.StartDate, EffectiveDate)") {
		t.Fatalf("rewritten expr = %q, want relation comparison wrapped", rewritten)
	}
	if strings.Contains(rewritten, "STRICTSAFEGT(Amount, 0)") {
		t.Fatalf("rewritten expr = %q, want non-relation comparison unchanged", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionSupportsFunctionOperandOnRight(t *testing.T) {
	expr := `Contract.StartDate > TODAY()`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if !strings.Contains(rewritten, "STRICTSAFEGT(Contract.StartDate, TODAY())") {
		t.Fatalf("rewritten expr = %q, want function operand preserved", rewritten)
	}
	if strings.Contains(rewritten, "STRICTSAFEGT(Contract.StartDate, TODAY)()") {
		t.Fatalf("rewritten expr = %q, want no broken function split", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionSupportsNestedFunctionComparisons(t *testing.T) {
	expr := `AND(Contract.StartDate > TODAY(), Amount > 0)`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if !strings.Contains(rewritten, "AND(STRICTSAFEGT(Contract.StartDate, TODAY()), Amount > 0)") {
		t.Fatalf("rewritten expr = %q, want nested relation comparison preserved", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionSupportsGTEComparisons(t *testing.T) {
	expr := `Contract.StartDate >= EffectiveDate`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if !strings.Contains(rewritten, "STRICTSAFEGTE(Contract.StartDate, EffectiveDate)") {
		t.Fatalf("rewritten expr = %q, want GTE relation comparison wrapped", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionLeavesParenthesizedRightOperandUntouched(t *testing.T) {
	expr := `Contract.StartDate > (TODAY())`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if rewritten != expr {
		t.Fatalf("rewritten expr = %q, want parenthesized right operand unchanged", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionSupportsRecordScopedRelationComparisons(t *testing.T) {
	expr := `$Record.Contract.StartDate > EffectiveDate`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if !strings.Contains(rewritten, "STRICTSAFEGT($Record.Contract.StartDate, EffectiveDate)") {
		t.Fatalf("rewritten expr = %q, want record-scoped relation comparison wrapped", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionSupportsChainedRelationComparisons(t *testing.T) {
	expr := `Parent.Parent.StartDate > EffectiveDate`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if !strings.Contains(rewritten, "STRICTSAFEGT(Parent.Parent.StartDate, EffectiveDate)") {
		t.Fatalf("rewritten expr = %q, want chained relation comparison wrapped", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionSupportsRelationOperandOnRight(t *testing.T) {
	expr := `Unit_Net_Price__c < Quote_Product__r.Unit_Net_Price__c`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if !strings.Contains(rewritten, "STRICTSAFELT(Unit_Net_Price__c, Quote_Product__r.Unit_Net_Price__c)") {
		t.Fatalf("rewritten expr = %q, want right-hand relation comparison wrapped", rewritten)
	}
}

func TestRewriteValidationFormulaExpressionPreservesQuotedComparisons(t *testing.T) {
	expr := `Error_Message__c = "Contract.StartDate > EffectiveDate"`

	rewritten := rewriteValidationFormulaExpression(expr, nil)

	if strings.Contains(rewritten, "SAFEGT(") {
		t.Fatalf("rewritten expr = %q, want quoted string untouched", rewritten)
	}
}

func TestExtractValidationRelationRootsIgnoresPermissionGlobal(t *testing.T) {
	roots := extractValidationRelationRoots(`$Permission.ActivateOrder != true && $Profile.Name != "" && $UserRole.Name != "" && "Quoted.Relation" != "" && Contract.StartDate > EffectiveDate`)

	if len(roots) != 1 || roots[0] != "Contract" {
		t.Fatalf("extractValidationRelationRoots returned %#v, want only Contract", roots)
	}
}

func TestExtractValidationRelationRootsIgnoresAllSupportedGlobals(t *testing.T) {
	roots := extractValidationRelationRoots(`$User.Name != "" && $Profile.Name != "" && $Permission.RunFlow != true && $UserRole.Name != "" && Contract.StartDate > EffectiveDate`)

	if len(roots) != 1 || roots[0] != "Contract" {
		t.Fatalf("extractValidationRelationRoots returned %#v, want only Contract", roots)
	}
}

func TestValidateFieldMetadataRejectsBlankFormulaExpression(t *testing.T) {
	vs := NewValidationService(nil)
	formula := "   "
	returnType := constants.FieldTypeString

	err := vs.ValidateFieldMetadata(&models.FieldMetadata{
		APIName:    "Computed__c",
		Label:      "Computed",
		Type:       constants.FieldTypeFormula,
		Formula:    &formula,
		ReturnType: &returnType,
	})
	if err == nil {
		t.Fatal("ValidateFieldMetadata blank formula error = nil, want rejection")
	}
	if !strings.Contains(err.Error(), "Formula fields require a formula expression") {
		t.Fatalf("ValidateFieldMetadata error = %v, want formula expression rejection", err)
	}
}

func TestValidateFormulaExpressionDeclaredType(t *testing.T) {
	formula := "Amount__c * 2"
	blankFormula := "   "
	whitespaceFormula := "\n\t  "

	cases := []struct {
		name            string
		objectAPIName   string
		fieldAPIName    string
		declaredType    models.FieldType
		formula         *string
		wantErr         bool
		wantErrExactMsg string
	}{
		{
			name:          "non_formula_type_with_expression_rejected",
			objectAPIName: "Widget__c",
			fieldAPIName:  "Score__c",
			declaredType:  constants.FieldTypeString,
			formula:       &formula,
			wantErr:       true,
			wantErrExactMsg: FormulaExpressionOnNonFormulaTypeError(
				"Widget__c",
				"Score__c",
				constants.FieldTypeString,
			).Error(),
		},
		{
			name:          "formula_type_with_expression_allowed",
			objectAPIName: "Widget__c",
			fieldAPIName:  "Score__c",
			declaredType:  constants.FieldTypeFormula,
			formula:       &formula,
		},
		{
			name:          "non_formula_type_with_blank_expression_allowed",
			objectAPIName: "Widget__c",
			fieldAPIName:  "Score__c",
			declaredType:  constants.FieldTypeString,
			formula:       &blankFormula,
		},
		{
			name:          "non_formula_type_with_whitespace_expression_allowed",
			objectAPIName: "Widget__c",
			fieldAPIName:  "Score__c",
			declaredType:  constants.FieldTypeString,
			formula:       &whitespaceFormula,
		},
		{
			name:          "non_formula_type_with_nil_expression_allowed",
			objectAPIName: "Widget__c",
			fieldAPIName:  "Score__c",
			declaredType:  constants.FieldTypeString,
			formula:       nil,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := ValidateFormulaExpressionDeclaredType(tc.objectAPIName, tc.fieldAPIName, tc.declaredType, tc.formula)
			if tc.wantErr && err == nil {
				t.Fatal("ValidateFormulaExpressionDeclaredType = nil, want error")
			}
			if !tc.wantErr && err != nil {
				t.Fatalf("ValidateFormulaExpressionDeclaredType = %v, want nil", err)
			}
			if tc.wantErr && err.Error() != tc.wantErrExactMsg {
				t.Fatalf("ValidateFormulaExpressionDeclaredType error = %q, want %q", err.Error(), tc.wantErrExactMsg)
			}
		})
	}
}

// Pins the delegation contract: validation_service must not restate the
// session-bound formula global names with a local switch or map. Reading
// through formula.IsSessionFormulaGlobalRoot is the single read path.
// CLAUDE.md "Coding Red Lines" prohibits SObject-name branches inside
// generic engines; if a future change re-adds a literal "Profile" / "User"
// / "UserRole" / "Permission" branch here, this test plus the formula-pkg
// helper test together force it to be moved back to the SSoT.
func TestValidationFormulaGlobalRootDelegatesToFormulaPackage(t *testing.T) {
	for _, name := range []string{
		"User", "Profile", "UserRole", "Permission",
		"__User", "__Profile", "__UserRole", "__Permission",
	} {
		if !validationFormulaGlobalRoot(name) {
			t.Errorf("validationFormulaGlobalRoot(%q) = false; want true (session global)", name)
		}
		if formula.IsSessionFormulaGlobalRoot(name) != validationFormulaGlobalRoot(name) {
			t.Errorf("validationFormulaGlobalRoot(%q) diverged from formula.IsSessionFormulaGlobalRoot — must delegate", name)
		}
	}
	for _, name := range []string{"Contract", "Account", "UserSession", ""} {
		if validationFormulaGlobalRoot(name) {
			t.Errorf("validationFormulaGlobalRoot(%q) = true; want false", name)
		}
	}
}

// Extending extractValidationRelationRoots without the literal global names:
// when the formula references an unknown root that *looks* SObject-shaped
// (`Profile_Backup.Name`) but is NOT a session global, it must surface as a
// real relation root for the resolver to handle. Guards against accidentally
// widening the global list back to a substring/prefix match.
func TestExtractValidationRelationRootsKeepsSimilarNamedRelations(t *testing.T) {
	roots := extractValidationRelationRoots(`Profile_Backup.Name != "" && Contract.StartDate > EffectiveDate`)

	want := map[string]bool{"Profile_Backup": true, "Contract": true}
	if len(roots) != len(want) {
		t.Fatalf("extractValidationRelationRoots returned %#v, want %v", roots, want)
	}
	for _, r := range roots {
		if !want[r] {
			t.Errorf("unexpected root %q in %#v", r, roots)
		}
	}
}

func TestValidateRecordBlocksDeclaredMissingRelationComparison(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetFormulaRelationResolver(func(_ context.Context, _ string, _ models.SObject, _ string) (models.SObject, error) {
		return nil, nil
	})

	schema := validationRuleTestSchema()
	record := models.SObject{"EffectiveDate": "2026-06-01"}
	rules := []*models.ValidationRule{{
		Name:         "DeclaredMissingRelation",
		Active:       true,
		Condition:    "Contract.StartDate > EffectiveDate",
		ErrorMessage: "should block",
	}}

	if err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil); err == nil {
		t.Fatal("ValidateRecord declared missing relation error = nil, want blocking validation error")
	}
}

func TestValidateRecordUsesRequestBoundFormulaEngineForPicklistValidation(t *testing.T) {
	bundle, err := formula.ParseFunctionBundlePayload([]byte(`{
		"functions": [{
			"name": "ISPICKVAL",
			"minArgs": 2,
			"maxArgs": 2,
			"return": {"kind": "fixed", "type": "boolean"},
			"args": [
				{"type": "any", "nullable": true},
				{"type": "string", "nullable": true}
			],
			"evaluator": {
				"kind": "formula_expr_v1",
				"params": ["value", "expected"],
				"body": "value == expected"
			}
		}]
	}`))
	if err != nil {
		t.Fatalf("ParseFunctionBundlePayload: %v", err)
	}
	requestEngine := formula.NewEngine(formula.WithoutRuntimeFunctionBundles())
	if err := requestEngine.RegisterFunctionBundles([]formula.FunctionBundle{bundle}); err != nil {
		t.Fatalf("RegisterFunctionBundles: %v", err)
	}

	vs := NewValidationService(formula.NewEngine(formula.WithoutRuntimeFunctionBundles()))
	resolverCalls := 0
	vs.SetFormulaEngineResolver(func(context.Context) (*formula.Engine, error) {
		resolverCalls++
		return requestEngine, nil
	})
	err = vs.ValidateRecord(
		context.Background(),
		models.SObject{"Status__c": "Submitted"},
		&models.ObjectMetadata{APIName: "WorkItem", Fields: []models.FieldMetadata{{
			APIName: "Status__c", Type: constants.FieldTypeString,
		}}},
		[]*models.ValidationRule{{
			Name: "SubmittedStatusGuard", Active: true,
			Condition: `ISPICKVAL(Status__c, "Submitted")`, ErrorMessage: "request-bound guard fired",
		}},
		nil,
		nil,
	)
	if err == nil || !strings.Contains(err.Error(), "request-bound guard fired") || strings.Contains(err.Error(), "failed to evaluate") {
		t.Fatalf("ValidateRecord() error = %v, want clean request-bound validation failure", err)
	}
	if resolverCalls != 1 {
		t.Fatalf("formula resolver calls = %d, want 1", resolverCalls)
	}
}

func TestValidateRecordFailsClosedOnUnknownRelationComparison(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetFormulaRelationResolver(func(_ context.Context, _ string, _ models.SObject, _ string) (models.SObject, error) {
		return nil, nil
	})

	schema := validationRuleTestSchema()
	record := models.SObject{"EffectiveDate": "2026-06-01"}
	rules := []*models.ValidationRule{{
		Name:         "UnknownRelation",
		Active:       true,
		Condition:    "MissingRelation.StartDate > EffectiveDate",
		ErrorMessage: "should fail closed",
	}}

	if err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil); err == nil {
		t.Fatal("ValidateRecord unknown relation error = nil, want fail-closed evaluation error")
	} else if !strings.Contains(err.Error(), "failed to evaluate") {
		t.Fatalf("ValidateRecord unknown relation error = %v, want evaluation failure", err)
	}
}

func TestValidateRecordNormalizesRelatedPercentReturningFormulaField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	metadataSvc := newValidationTestMetadataService(t,
		validationRuleTestSchema(),
		&models.ObjectMetadata{
			APIName: "Contract",
			Fields: []models.FieldMetadata{
				{
					APIName:    "FrozenScore__c",
					Type:       constants.FieldTypeFormula,
					ReturnType: validationFieldTypePtr(constants.FieldTypePercent),
				},
			},
		},
	)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			record: models.SObject{
				constants.FieldID: "800000000000001AAA",
				"FrozenScore__c":  40.0,
			},
		},
	))

	record := models.SObject{"ContractId": "800000000000001AAA"}
	rules := []*models.ValidationRule{{
		Name:         "RelatedPercentReturningFormulaThreshold",
		Active:       true,
		Condition:    "Contract.FrozenScore__c < 1",
		ErrorMessage: "related percent formula normalized",
	}}

	err := vs.ValidateRecord(context.Background(), record, validationRuleTestSchema(), rules, nil, nil)
	if err == nil || !strings.Contains(err.Error(), "related percent formula normalized") {
		t.Fatalf("ValidateRecord() error = %v, want validation error proving 40.0 normalized to 0.4 before comparison", err)
	}
}

func TestValidateRecordProjectsActiveComputedFieldsBeforeValidation(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	accountRel := "Account"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)

	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{
				APIName: "Region__c",
				Type:    constants.FieldTypePicklist,
			},
		},
	}

	tests := []struct {
		name        string
		fieldType   constants.SchemaFieldType
		formulaExpr string
		returnType  *models.FieldType
	}{
		{
			name:        "formula_typed_active_field",
			fieldType:   constants.FieldTypeFormula,
			formulaExpr: "TEXT(Account.Region__c)",
			returnType:  stringReturnType,
		},
		{
			name:        "legacy_scalar_active_computed_field",
			fieldType:   constants.FieldTypeString,
			formulaExpr: "Account.Region__c",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			opportunitySchema := &models.ObjectMetadata{
				APIName: "Opportunity",
				Fields: []models.FieldMetadata{
					{
						APIName:          "AccountId",
						Type:             constants.FieldTypeReference,
						ReferenceTo:      []string{"Account"},
						RelationshipName: &accountRel,
					},
					{
						APIName:     "Region__c",
						Type:        tt.fieldType,
						Formula:     validationStringPtr(tt.formulaExpr),
						FormulaMode: &mode,
						ReturnType:  tt.returnType,
					},
					{
						APIName: "PingCAP_Contracting_Entity_EMEA_only__c",
						Type:    constants.FieldTypeString,
					},
				},
			}

			metadataSvc := newValidationTestMetadataService(t, opportunitySchema, accountSchema)
			vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
				metadataSvc,
				validationRecordRepoStub{
					record: models.SObject{
						constants.FieldID: "001000000000001AAA",
						"Region__c":       "EMEA",
					},
				},
			))
			setValidationTestLegacyActiveFormulaResolver(vs)

			err := vs.ValidateRecord(
				context.Background(),
				models.SObject{
					"AccountId": "001000000000001AAA",
				},
				opportunitySchema,
				[]*models.ValidationRule{{
					Name:         "RequireEMEAContractingEntity",
					Active:       true,
					Condition:    `Region__c == "EMEA" && ISBLANK(PingCAP_Contracting_Entity_EMEA_only__c)`,
					ErrorMessage: "contracting entity is required for EMEA opportunities",
				}},
				nil,
				nil,
			)
			if err == nil {
				t.Fatal("ValidateRecord() error = nil, want validation failure after active field projection")
			}
			if !strings.Contains(err.Error(), "contracting entity is required for EMEA opportunities") {
				t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
			}
		})
	}
}

func TestValidateRecordProjectsActiveLookupValueFieldsBeforeValidation(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)
	boolReturnType := validationFieldTypePtr(constants.FieldTypeBoolean)
	productRel := "Product2"
	campaignRel := "Campaign"

	tests := []struct {
		name          string
		schema        *models.ObjectMetadata
		targetSchema  *models.ObjectMetadata
		record        models.SObject
		ruleCondition string
		queryRows     []models.SObject
		wantSQL       []string
		wantParams    []interface{}
	}{
		{
			name: "single_match_pricebook_entry_name_projection",
			schema: &models.ObjectMetadata{
				APIName: "PricebookEntry",
				Fields: []models.FieldMetadata{
					{
						APIName:          "Product2Id",
						Type:             constants.FieldTypeReference,
						ReferenceTo:      []string{"Product2"},
						RelationshipName: &productRel,
					},
					{
						APIName:     "Name",
						Type:        constants.FieldTypeString,
						FormulaMode: &mode,
						ReturnType:  stringReturnType,
						ProjectionConfig: &models.FieldProjectionConfig{
							Kind:         "lookupValue",
							TargetObject: "Product2",
							SelectField:  "Name",
							Matches: []models.FieldProjectionMatch{
								{TargetField: constants.FieldID, SourceField: "Product2Id"},
							},
							ExcludeDeleted: true,
						},
					},
					{
						APIName: "Required__c",
						Type:    constants.FieldTypeString,
					},
				},
			},
			targetSchema: &models.ObjectMetadata{
				APIName: "Product2",
				Fields: []models.FieldMetadata{
					{APIName: constants.FieldID, Type: constants.FieldTypeString},
					{APIName: "Name", Type: constants.FieldTypeString},
					{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean},
				},
			},
			record:        models.SObject{"Product2Id": "01t000000000001AAA"},
			ruleCondition: `Name == "Widget" && ISBLANK(Required__c)`,
			queryRows:     []models.SObject{{"Name": "Widget"}},
			wantSQL:       []string{"SELECT `Name` FROM `Product2`", "`Id` = ?", "`IsDeleted` = ?"},
			wantParams:    []interface{}{"01t000000000001AAA", false},
		},
		{
			name: "multi_match_campaign_member_status_projection",
			schema: &models.ObjectMetadata{
				APIName: "CampaignMember",
				Fields: []models.FieldMetadata{
					{
						APIName:          "CampaignId",
						Type:             constants.FieldTypeReference,
						ReferenceTo:      []string{"Campaign"},
						RelationshipName: &campaignRel,
					},
					{
						APIName: "Status",
						Type:    constants.FieldTypeString,
					},
					{
						APIName:     "HasResponded",
						Type:        constants.FieldTypeBoolean,
						FormulaMode: &mode,
						ReturnType:  boolReturnType,
						ProjectionConfig: &models.FieldProjectionConfig{
							Kind:         "lookupValue",
							TargetObject: "CampaignMemberStatus",
							SelectField:  "HasResponded",
							Matches: []models.FieldProjectionMatch{
								{TargetField: "CampaignId", SourceField: "CampaignId"},
								{TargetField: "Label", SourceField: "Status"},
							},
							ExcludeDeleted: true,
						},
					},
					{
						APIName: "Required__c",
						Type:    constants.FieldTypeString,
					},
				},
			},
			targetSchema: &models.ObjectMetadata{
				APIName: "CampaignMemberStatus",
				Fields: []models.FieldMetadata{
					{APIName: "CampaignId", Type: constants.FieldTypeReference},
					{APIName: "Label", Type: constants.FieldTypeString},
					{APIName: "HasResponded", Type: constants.FieldTypeBoolean},
					{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean},
				},
			},
			record:        models.SObject{"CampaignId": "701000000000001AAA", "Status": "Sent"},
			ruleCondition: `HasResponded == true && ISBLANK(Required__c)`,
			queryRows:     []models.SObject{{"HasResponded": true}},
			wantSQL:       []string{"SELECT `HasResponded` FROM `CampaignMemberStatus`", "`CampaignId` = ?", "`Label` = ?", "`IsDeleted` = ?"},
			wantParams:    []interface{}{"701000000000001AAA", "Sent", false},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			metadataSvc := newValidationTestMetadataService(t, tc.schema, tc.targetSchema)
			queryExec := &validationQueryExecutorStub{rows: tc.queryRows}
			vs.SetActiveProjectionResolver(NewValidationActiveProjectionResolver(metadataSvc, queryExec))

			err := vs.ValidateRecord(
				context.Background(),
				tc.record,
				tc.schema,
				[]*models.ValidationRule{{
					Name:         "RequireProjectedValue",
					Active:       true,
					Condition:    tc.ruleCondition,
					ErrorMessage: "projected active field should be visible to validation",
				}},
				nil,
				nil,
			)
			if err == nil {
				t.Fatal("ValidateRecord() error = nil, want validation failure after active projection")
			}
			if !strings.Contains(err.Error(), "projected active field should be visible to validation") {
				t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
			}
			for _, fragment := range tc.wantSQL {
				if !strings.Contains(queryExec.lastSQL, fragment) {
					t.Fatalf("ExecuteRawSQL() SQL = %q, want fragment %q", queryExec.lastSQL, fragment)
				}
			}
			if len(queryExec.lastParams) != len(tc.wantParams) {
				t.Fatalf("ExecuteRawSQL() params = %#v, want %#v", queryExec.lastParams, tc.wantParams)
			}
			for i, want := range tc.wantParams {
				if queryExec.lastParams[i] != want {
					t.Fatalf("ExecuteRawSQL() params[%d] = %#v, want %#v", i, queryExec.lastParams[i], want)
				}
			}
		})
	}
}

func TestValidateRecordProjectsBlankStringLookupValueMatchesBeforeValidation(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "CampaignMember",
		Fields: []models.FieldMetadata{
			{
				APIName: "Status",
				Type:    constants.FieldTypeString,
			},
			{
				APIName:     "ProjectedLabel__c",
				Type:        constants.FieldTypeString,
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
				ProjectionConfig: &models.FieldProjectionConfig{
					Kind:         "lookupValue",
					TargetObject: "StatusCatalog__c",
					SelectField:  "Label__c",
					Matches: []models.FieldProjectionMatch{
						{TargetField: "ExternalKey__c", SourceField: "Status"},
					},
					ExcludeDeleted: true,
				},
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}
	targetSchema := &models.ObjectMetadata{
		APIName: "StatusCatalog__c",
		Fields: []models.FieldMetadata{
			{APIName: "ExternalKey__c", Type: constants.FieldTypeString},
			{APIName: "Label__c", Type: constants.FieldTypeString},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean},
		},
	}

	metadataSvc := newValidationTestMetadataService(t, schema, targetSchema)
	queryExec := &validationQueryExecutorStub{
		handler: func(sql string, params []interface{}) ([]models.SObject, error) {
			if len(params) != 2 {
				t.Fatalf("ExecuteRawSQL() params = %#v, want blank key and IsDeleted flag", params)
			}
			if got, ok := params[0].(string); !ok || got != "" {
				t.Fatalf("ExecuteRawSQL() first param = %#v, want blank string", params[0])
			}
			return []models.SObject{{"Label__c": "Matched"}}, nil
		},
	}
	vs.SetActiveProjectionResolver(NewValidationActiveProjectionResolver(metadataSvc, queryExec))

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{"Status": ""},
		schema,
		[]*models.ValidationRule{{
			Name:         "BlankKeyProjectionShouldMatch",
			Active:       true,
			Condition:    `ProjectedLabel__c == "Matched" && ISBLANK(Required__c)`,
			ErrorMessage: "blank-string projection key should still resolve",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure after blank-string projection match")
	}
	if !strings.Contains(err.Error(), "blank-string projection key should still resolve") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
	if len(queryExec.calls) < 1 {
		t.Fatalf("ExecuteRawSQL() calls = %d, want at least 1", len(queryExec.calls))
	}
}

func TestValidateRecordProjectionExcludeDeletedMatchesCompilerNullSemantics(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "CampaignMember",
		Fields: []models.FieldMetadata{
			{
				APIName: "Status",
				Type:    constants.FieldTypeString,
			},
			{
				APIName:     "ProjectedLabel__c",
				Type:        constants.FieldTypeString,
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
				ProjectionConfig: &models.FieldProjectionConfig{
					Kind:         "lookupValue",
					TargetObject: "StatusCatalog__c",
					SelectField:  "Label__c",
					Matches: []models.FieldProjectionMatch{
						{TargetField: "ExternalKey__c", SourceField: "Status"},
					},
					ExcludeDeleted: true,
				},
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}
	targetSchema := &models.ObjectMetadata{
		APIName: "StatusCatalog__c",
		Fields: []models.FieldMetadata{
			{APIName: "ExternalKey__c", Type: constants.FieldTypeString},
			{APIName: "Label__c", Type: constants.FieldTypeString},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean},
		},
	}

	metadataSvc := newValidationTestMetadataService(t, schema, targetSchema)
	queryExec := &validationQueryExecutorStub{
		handler: func(sql string, params []interface{}) ([]models.SObject, error) {
			if !strings.Contains(sql, "(`IsDeleted` = ? OR `IsDeleted` IS NULL)") {
				t.Fatalf("ExecuteRawSQL() SQL = %q, want nullable IsDeleted predicate", sql)
			}
			return []models.SObject{{"Label__c": "Matched"}}, nil
		},
	}
	vs.SetActiveProjectionResolver(NewValidationActiveProjectionResolver(metadataSvc, queryExec))

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{"Status": "code-1"},
		schema,
		[]*models.ValidationRule{{
			Name:         "NullableIsDeletedProjectionShouldMatch",
			Active:       true,
			Condition:    `ProjectedLabel__c == "Matched" && ISBLANK(Required__c)`,
			ErrorMessage: "nullable IsDeleted rows should remain visible",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure after nullable IsDeleted projection match")
	}
	if !strings.Contains(err.Error(), "nullable IsDeleted rows should remain visible") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
	if len(queryExec.calls) < 1 {
		t.Fatalf("ExecuteRawSQL() calls = %d, want at least 1", len(queryExec.calls))
	}
}

func TestValidateRecordProjectsAllActiveFieldsThroughConfiguredResolver(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)
	productRel := "Product2"

	schema := &models.ObjectMetadata{
		APIName: "PricebookEntry",
		Fields: []models.FieldMetadata{
			{
				APIName:          "Product2Id",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"Product2"},
				RelationshipName: &productRel,
			},
			{
				APIName:     "Name",
				Type:        constants.FieldTypeString,
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
				ProjectionConfig: &models.FieldProjectionConfig{
					Kind:         "lookupValue",
					TargetObject: "Product2",
					SelectField:  "Name",
					Matches: []models.FieldProjectionMatch{
						{TargetField: constants.FieldID, SourceField: "Product2Id"},
					},
					ExcludeDeleted: true,
				},
			},
			{
				APIName:     "DisplayName__c",
				Type:        constants.FieldTypeFormula,
				Formula:     validationStringPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
			},
			{
				APIName:     "Unsupported__c",
				Type:        constants.FieldTypeFormula,
				Formula:     validationStringPtr(`LOOKUPVALUE("Product2", "Name", "Id", Product2Id)`),
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}
	targetSchema := &models.ObjectMetadata{
		APIName: "Product2",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean},
		},
	}

	metadataSvc := newValidationTestMetadataService(t, schema, targetSchema)
	queryExec := &validationQueryExecutorStub{rows: []models.SObject{{"Name": "Widget"}}}
	vs.SetActiveProjectionResolver(NewValidationActiveProjectionResolver(metadataSvc, queryExec))
	vs.SetActiveFormulaResolver(func(ctx context.Context, schema *models.ObjectMetadata, record models.SObject, field *models.FieldMetadata) (interface{}, error) {
		switch field.APIName {
		case "DisplayName__c":
			return record["Name"], nil
		case "Unsupported__c":
			return "\n", nil
		default:
			return nil, nil
		}
	})

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{"Product2Id": "01t000000000001AAA"},
		schema,
		[]*models.ValidationRule{{
			Name:         "RequireConfiguredResolverProjection",
			Active:       true,
			Condition:    `DisplayName__c == "Widget" && ISBLANK(Required__c)`,
			ErrorMessage: "configured active formula resolver should project validation-visible fields",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure after active projection")
	}
	if !strings.Contains(err.Error(), "configured active formula resolver should project validation-visible fields") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
}

func TestValidateRecordProjectsProjectionDependenciesFromActiveSourceFields(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)
	productRel := "Product2"

	schema := &models.ObjectMetadata{
		APIName: "PricebookEntry",
		Fields: []models.FieldMetadata{
			{
				APIName:          "Product2Id",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"Product2"},
				RelationshipName: &productRel,
			},
			{
				APIName:     "Name",
				Type:        constants.FieldTypeString,
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
				ProjectionConfig: &models.FieldProjectionConfig{
					Kind:         "lookupValue",
					TargetObject: "Product2",
					SelectField:  "Name",
					Matches: []models.FieldProjectionMatch{
						{TargetField: constants.FieldID, SourceField: "Product2Id"},
					},
					ExcludeDeleted: true,
				},
			},
			{
				APIName:     "Label__c",
				Type:        constants.FieldTypeString,
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
				ProjectionConfig: &models.FieldProjectionConfig{
					Kind:         "lookupValue",
					TargetObject: "ProductLabel__c",
					SelectField:  "Label__c",
					Matches: []models.FieldProjectionMatch{
						{TargetField: "ProductName__c", SourceField: "Name"},
					},
				},
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}
	productSchema := &models.ObjectMetadata{
		APIName: "Product2",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean},
		},
	}
	labelSchema := &models.ObjectMetadata{
		APIName: "ProductLabel__c",
		Fields: []models.FieldMetadata{
			{APIName: "ProductName__c", Type: constants.FieldTypeString},
			{APIName: "Label__c", Type: constants.FieldTypeString},
		},
	}

	metadataSvc := newValidationTestMetadataService(t, schema, productSchema, labelSchema)
	queryExec := &validationQueryExecutorStub{
		handler: func(sql string, params []interface{}) ([]models.SObject, error) {
			switch {
			case strings.Contains(sql, "FROM `Product2`"):
				return []models.SObject{{"Name": "Widget"}}, nil
			case strings.Contains(sql, "FROM `ProductLabel__c`"):
				return []models.SObject{{"Label__c": "Widget Label"}}, nil
			default:
				return nil, nil
			}
		},
	}
	vs.SetActiveProjectionResolver(NewValidationActiveProjectionResolver(metadataSvc, queryExec))

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{"Product2Id": "01t000000000001AAA"},
		schema,
		[]*models.ValidationRule{{
			Name:         "RequireProjectionDependency",
			Active:       true,
			Condition:    `Label__c == "Widget Label" && ISBLANK(Required__c)`,
			ErrorMessage: "projection dependencies should include active source fields",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure after projection dependency resolution")
	}
	if !strings.Contains(err.Error(), "projection dependencies should include active source fields") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
	if len(queryExec.calls) < 2 {
		t.Fatalf("ExecuteRawSQL() calls = %d, want at least 2", len(queryExec.calls))
	}
	if !strings.Contains(queryExec.calls[0].sql, "FROM `Product2`") {
		t.Fatalf("first ExecuteRawSQL() SQL = %q, want Product2 lookup", queryExec.calls[0].sql)
	}
	if !strings.Contains(queryExec.calls[1].sql, "FROM `ProductLabel__c`") {
		t.Fatalf("second ExecuteRawSQL() SQL = %q, want ProductLabel__c lookup", queryExec.calls[1].sql)
	}
	if len(queryExec.calls[1].params) != 1 || queryExec.calls[1].params[0] != "Widget" {
		t.Fatalf("second ExecuteRawSQL() params = %#v, want [\"Widget\"]", queryExec.calls[1].params)
	}
}

func TestValidateRecordProjectionMatchesUseRawNullableSourceValues(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)

	tests := []struct {
		name        string
		sourceField models.FieldMetadata
		record      models.SObject
		matches     []models.FieldProjectionMatch
		targetField models.FieldMetadata
	}{
		{
			name: "nullable numeric source stays null",
			sourceField: models.FieldMetadata{
				APIName: "Rank__c",
				Type:    constants.FieldTypeDouble,
			},
			record: models.SObject{},
			matches: []models.FieldProjectionMatch{
				{TargetField: "SortOrder__c", SourceField: "Rank__c"},
			},
			targetField: models.FieldMetadata{
				APIName: "SortOrder__c",
				Type:    constants.FieldTypeDouble,
			},
		},
		{
			name: "nullable boolean source stays null",
			sourceField: models.FieldMetadata{
				APIName: "Enabled__c",
				Type:    constants.FieldTypeBoolean,
			},
			record: models.SObject{},
			matches: []models.FieldProjectionMatch{
				{TargetField: "Enabled__c", SourceField: "Enabled__c"},
			},
			targetField: models.FieldMetadata{
				APIName: "Enabled__c",
				Type:    constants.FieldTypeBoolean,
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			schema := &models.ObjectMetadata{
				APIName: "Source__c",
				Fields: []models.FieldMetadata{
					tc.sourceField,
					{
						APIName:     "Projected__c",
						Type:        constants.FieldTypeString,
						FormulaMode: &mode,
						ReturnType:  stringReturnType,
						ProjectionConfig: &models.FieldProjectionConfig{
							Kind:           "lookupValue",
							TargetObject:   "Lookup__c",
							SelectField:    "Label__c",
							Matches:        tc.matches,
							ExcludeDeleted: true,
						},
					},
					{
						APIName: "Required__c",
						Type:    constants.FieldTypeString,
					},
				},
			}
			targetSchema := &models.ObjectMetadata{
				APIName: "Lookup__c",
				Fields: []models.FieldMetadata{
					tc.targetField,
					{APIName: "Label__c", Type: constants.FieldTypeString},
					{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean},
				},
			}

			metadataSvc := newValidationTestMetadataService(t, schema, targetSchema)
			queryExec := &validationQueryExecutorStub{
				handler: func(sql string, params []interface{}) ([]models.SObject, error) {
					t.Fatalf("ExecuteRawSQL() should not run when nullable projection source is absent; sql=%q params=%#v", sql, params)
					return nil, nil
				},
			}
			vs.SetActiveProjectionResolver(NewValidationActiveProjectionResolver(metadataSvc, queryExec))

			err := vs.ValidateRecord(
				context.Background(),
				tc.record,
				schema,
				[]*models.ValidationRule{{
					Name:         "NullableProjectionSourceShouldStayNull",
					Active:       true,
					Condition:    `Projected__c == "Matched" && ISBLANK(Required__c)`,
					ErrorMessage: "raw nullable projection source should stay null",
				}},
				nil,
				nil,
			)
			if err != nil {
				t.Fatalf("ValidateRecord() error = %v, want nil", err)
			}
			if len(queryExec.calls) != 0 {
				t.Fatalf("ExecuteRawSQL() calls = %d, want 0", len(queryExec.calls))
			}
		})
	}
}

func TestValidateRecordActiveFormulaResolverReceivesRawNullableSourceValues(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	numberReturnType := validationFieldTypePtr(constants.FieldTypeDouble)
	boolReturnType := validationFieldTypePtr(constants.FieldTypeBoolean)

	schema := &models.ObjectMetadata{
		APIName: "Source__c",
		Fields: []models.FieldMetadata{
			{
				APIName: "Rank__c",
				Type:    constants.FieldTypeDouble,
			},
			{
				APIName: "Enabled__c",
				Type:    constants.FieldTypeBoolean,
			},
			{
				APIName:     "RankEcho__c",
				Type:        constants.FieldTypeFormula,
				Formula:     validationStringPtr("Rank__c"),
				FormulaMode: &mode,
				ReturnType:  numberReturnType,
			},
			{
				APIName:     "EnabledEcho__c",
				Type:        constants.FieldTypeFormula,
				Formula:     validationStringPtr("Enabled__c"),
				FormulaMode: &mode,
				ReturnType:  boolReturnType,
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}

	seenRank := false
	seenEnabled := false
	vs.SetActiveFormulaResolver(func(ctx context.Context, schema *models.ObjectMetadata, record models.SObject, field *models.FieldMetadata) (interface{}, error) {
		switch field.APIName {
		case "RankEcho__c":
			seenRank = true
			if value, ok := record["Rank__c"]; ok && value != nil {
				t.Fatalf("activeFormula(record)[Rank__c] = %#v, want absent/nil raw numeric source", value)
			}
			return nil, nil
		case "EnabledEcho__c":
			seenEnabled = true
			value, ok := record["Enabled__c"]
			if !ok {
				t.Fatal("activeFormula(record) missing Enabled__c, want explicit nil raw boolean source preserved")
			}
			if value != nil {
				t.Fatalf("activeFormula(record)[Enabled__c] = %#v, want nil raw boolean source", value)
			}
			return nil, nil
		default:
			return nil, nil
		}
	})

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{"Enabled__c": nil},
		schema,
		[]*models.ValidationRule{{
			Name:         "ActiveFormulaResolverShouldSeeRawNulls",
			Active:       true,
			Condition:    `ISBLANK(RankEcho__c) && ISBLANK(EnabledEcho__c) && ISBLANK(Required__c)`,
			ErrorMessage: "active formula resolver should see raw null sources",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure after active formula overlay")
	}
	if !strings.Contains(err.Error(), "active formula resolver should see raw null sources") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
	if !seenRank || !seenEnabled {
		t.Fatalf("active formula resolver calls seen = rank:%v enabled:%v, want both true", seenRank, seenEnabled)
	}
}

func TestValidateRecordProjectsMultiHopActiveComputedFieldsBeforeValidation(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	rootToA := "A"
	aToB := "B"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)

	rootSchema := &models.ObjectMetadata{
		APIName: "Root__c",
		Fields: []models.FieldMetadata{
			{
				APIName:          "AId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"A__c"},
				RelationshipName: &rootToA,
			},
			{
				APIName:     "ProjectedName__c",
				Type:        constants.FieldTypeFormula,
				Formula:     validationStringPtr("$Record.A.B.Name"),
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}
	aSchema := &models.ObjectMetadata{
		APIName: "A__c",
		Fields: []models.FieldMetadata{
			{
				APIName:          "BId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"B__c"},
				RelationshipName: &aToB,
			},
		},
	}
	bSchema := &models.ObjectMetadata{
		APIName: "B__c",
		Fields: []models.FieldMetadata{
			{
				APIName: "Name",
				Type:    constants.FieldTypeString,
			},
		},
	}

	metadataSvc := newValidationTestMetadataService(t, rootSchema, aSchema, bSchema)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			records: map[string]models.SObject{
				"A__c/a-001": {
					constants.FieldID: "a-001",
					"BId":             "b-001",
				},
				"B__c/b-001": {
					constants.FieldID: "b-001",
					"Name":            "leaf-two",
				},
			},
		},
	))
	setValidationTestLegacyActiveFormulaResolver(vs)

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{
			"AId": "a-001",
		},
		rootSchema,
		[]*models.ValidationRule{{
			Name:         "RequireProjectedLeaf",
			Active:       true,
			Condition:    `ProjectedName__c == "leaf-two" && ISBLANK(Required__c)`,
			ErrorMessage: "multi-hop active field should be visible to validation",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure after multi-hop active projection")
	}
	if !strings.Contains(err.Error(), "multi-hop active field should be visible to validation") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
}

func TestValidateRecordAllowsNullFirstHopInReferencedMultiHopActiveField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	rootToA := "A"
	aToB := "B"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)

	rootSchema := &models.ObjectMetadata{
		APIName: "Root__c",
		Fields: []models.FieldMetadata{
			{
				APIName:          "AId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"A__c"},
				RelationshipName: &rootToA,
			},
			{
				APIName:     "ProjectedName__c",
				Type:        constants.FieldTypeFormula,
				Formula:     validationStringPtr("$Record.A.B.Name"),
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}
	aSchema := &models.ObjectMetadata{
		APIName: "A__c",
		Fields: []models.FieldMetadata{
			{
				APIName:          "BId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"B__c"},
				RelationshipName: &aToB,
			},
		},
	}
	bSchema := &models.ObjectMetadata{
		APIName: "B__c",
		Fields: []models.FieldMetadata{
			{
				APIName: "Name",
				Type:    constants.FieldTypeString,
			},
		},
	}

	metadataSvc := newValidationTestMetadataService(t, rootSchema, aSchema, bSchema)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{},
	))
	setValidationTestLegacyActiveFormulaResolver(vs)

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{},
		rootSchema,
		[]*models.ValidationRule{{
			Name:         "RequireNullLeafHandling",
			Active:       true,
			Condition:    `ISBLANK(ProjectedName__c) && ISBLANK(Required__c)`,
			ErrorMessage: "null first hop should stay null during active projection",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure with null first-hop semantics")
	}
	if !strings.Contains(err.Error(), "null first hop should stay null during active projection") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
}

func TestValidateRecordIgnoresFlatDisplayNameStubsForMultiHopActiveField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	mode := "active"
	rootToA := "A"
	aToB := "B"
	stringReturnType := validationFieldTypePtr(constants.FieldTypeString)

	rootSchema := &models.ObjectMetadata{
		APIName: "Root__c",
		Fields: []models.FieldMetadata{
			{
				APIName:          "AId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"A__c"},
				RelationshipName: &rootToA,
			},
			{
				APIName:     "ProjectedName__c",
				Type:        constants.FieldTypeFormula,
				Formula:     validationStringPtr("$Record.A.B.Name"),
				FormulaMode: &mode,
				ReturnType:  stringReturnType,
			},
			{
				APIName: "Required__c",
				Type:    constants.FieldTypeString,
			},
		},
	}
	aSchema := &models.ObjectMetadata{
		APIName: "A__c",
		Fields: []models.FieldMetadata{
			{
				APIName:          "BId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"B__c"},
				RelationshipName: &aToB,
			},
		},
	}
	bSchema := &models.ObjectMetadata{
		APIName: "B__c",
		Fields: []models.FieldMetadata{
			{
				APIName: "Name",
				Type:    constants.FieldTypeString,
			},
		},
	}

	metadataSvc := newValidationTestMetadataService(t, rootSchema, aSchema, bSchema)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			records: map[string]models.SObject{
				"A__c/a-001": {
					constants.FieldID: "a-001",
					"BId":             "b-001",
				},
				"B__c/b-001": {
					constants.FieldID: "b-001",
					"Name":            "leaf-two",
				},
			},
		},
	))
	setValidationTestLegacyActiveFormulaResolver(vs)

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{
			"AId":      "a-001",
			"AId_Name": "display-only",
		},
		rootSchema,
		[]*models.ValidationRule{{
			Name:         "RequireFetchedParentForMultiHop",
			Active:       true,
			Condition:    `ProjectedName__c == "leaf-two" && ISBLANK(Required__c)`,
			ErrorMessage: "multi-hop active field should fetch the real parent row",
		}},
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure after real-parent fetch")
	}
	if !strings.Contains(err.Error(), "multi-hop active field should fetch the real parent row") {
		t.Fatalf("ValidateRecord() error = %v, want validation-rule message", err)
	}
}

func TestValidateRecordUsesStoredRecordTypeDeveloperName(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	recordTypeName := "Contract Only (APAC)"
	recordTypeDeveloperName := utils.SanitizeDeveloperName(recordTypeName)
	ownerPack := "salesforce-crm-standard"
	recordTypeRelationship := constants.ObjectRecordType
	schema := &models.ObjectMetadata{
		APIName: "Order",
		Fields: []models.FieldMetadata{
			{
				APIName:          constants.FieldRecordTypeID,
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{constants.ObjectRecordType},
				RelationshipName: &recordTypeRelationship,
			},
		},
	}
	metadataSvc := newValidationTestMetadataService(t,
		schema,
		recordTypeValidationPseudoSchema(),
	)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			record: models.SObject{
				constants.FieldID:   "012000000000001AAA",
				constants.FieldName: recordTypeName,
				"DeveloperName":     recordTypeDeveloperName,
			},
		},
	))

	record := models.SObject{constants.FieldRecordTypeID: "012000000000001AAA"}
	rules := []*models.ValidationRule{{
		Name:         "StoredRecordTypeDeveloperName",
		Active:       true,
		Condition:    `RecordType.DeveloperName == "Contract_Only_APAC"`,
		ErrorMessage: "record type developer name matched canonical column",
		OwnerPack:    &ownerPack,
	}}

	err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil)
	if err == nil || !strings.Contains(err.Error(), "record type developer name matched canonical column") {
		t.Fatalf("ValidateRecord() error = %v, want validation failure proving stored DeveloperName was used", err)
	}
}

func TestValidateRecordUsesBoundCatalogRecordTypeWithoutPhysicalFanout(t *testing.T) {
	view := runtimeMutationMetadataServiceView(t)
	metadataSvc, err := NewRuntimeMetadataService(RuntimeMetadataViewFromContext)
	if err != nil {
		t.Fatal(err)
	}
	repo := &validationCountingRecordRepoStub{
		record: models.SObject{
			constants.FieldID:                          "record-type-widget",
			constants.FieldName:                        "wrong physical name",
			constants.FieldSysRecordType_DeveloperName: "wrong_physical_name",
		},
	}
	vs := NewValidationService(formula.NewEngine())
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(metadataSvc, repo))
	schema := metadataSvc.GetSchema(WithRuntimeMetadataView(t.Context(), view), "Widget")
	if schema == nil {
		t.Fatal("bound mutation view did not expose Widget schema")
	}

	const ruleCount = 168
	rules := make([]*models.ValidationRule, 0, ruleCount)
	for i := 0; i < ruleCount; i++ {
		rules = append(rules, &models.ValidationRule{
			Name:         fmt.Sprintf("RecordTypeRule%03d", i),
			Active:       true,
			Condition:    `RecordType.DeveloperName == "NeverMatch"`,
			ErrorMessage: "must not fire",
		})
	}
	ctx := WithRuntimeMetadataView(t.Context(), view)
	err = vs.ValidateRecord(ctx, models.SObject{
		constants.FieldRecordTypeID: "record-type-widget",
	}, schema, rules, nil, nil)
	if err != nil {
		t.Fatalf("ValidateRecord() error = %v", err)
	}
	if repo.findOneCalls != 0 {
		t.Fatalf("physical RecordType reads = %d, want 0 for request-bound immutable Catalog identity", repo.findOneCalls)
	}

	firing := []*models.ValidationRule{{
		Name:         "CatalogRecordTypeSemantics",
		Active:       true,
		Condition:    `RecordType.Name == "WidgetType" && RecordType.DeveloperName == "WidgetType"`,
		ErrorMessage: "catalog record type matched",
	}}
	err = vs.ValidateRecord(ctx, models.SObject{
		constants.FieldRecordTypeID: "record-type-widget",
	}, schema, firing, nil, nil)
	if err == nil || !strings.Contains(err.Error(), "catalog record type matched") {
		t.Fatalf("ValidateRecord() error = %v, want Catalog Name/DeveloperName semantic parity", err)
	}
	if repo.findOneCalls != 0 {
		t.Fatalf("physical RecordType reads after semantic check = %d, want 0", repo.findOneCalls)
	}
}

func TestValidationRecordTypeBoundViewMissingFailsClosedWithoutPhysicalFallback(t *testing.T) {
	view := runtimeMutationMetadataServiceView(t)
	metadataSvc, err := NewRuntimeMetadataService(RuntimeMetadataViewFromContext)
	if err != nil {
		t.Fatal(err)
	}
	repo := &validationCountingRecordRepoStub{
		record: models.SObject{
			constants.FieldID:                          "missing-record-type",
			constants.FieldName:                        "side-table fallback",
			constants.FieldSysRecordType_DeveloperName: "side_table_fallback",
		},
	}
	resolver := NewValidationFormulaRelationResolver(metadataSvc, repo)
	ctx := WithRuntimeMetadataView(t.Context(), view)
	_, err = resolver(ctx, "Widget", models.SObject{
		constants.FieldRecordTypeID: "missing-record-type",
	}, constants.ObjectRecordType)
	if err == nil || !strings.Contains(err.Error(), "absent from the request-bound mutation metadata view") {
		t.Fatalf("resolver error = %v, want bound-view missing identity failure", err)
	}
	if repo.findOneCalls != 0 {
		t.Fatalf("physical fallback reads = %d, want 0 after bound-view miss", repo.findOneCalls)
	}
}

func TestValidationRecordTypeLegacyCompositionRetainsPhysicalLookup(t *testing.T) {
	recordTypeRelationship := constants.ObjectRecordType
	schema := &models.ObjectMetadata{
		APIName: "Widget",
		Fields: []models.FieldMetadata{{
			APIName: constants.FieldRecordTypeID, Type: constants.FieldTypeReference,
			ReferenceTo: []string{constants.ObjectRecordType}, RelationshipName: &recordTypeRelationship,
		}},
	}
	metadataSvc := newValidationTestMetadataService(t, schema, recordTypeValidationPseudoSchema())
	repo := &validationCountingRecordRepoStub{record: models.SObject{
		constants.FieldID:                          "record-type-widget",
		constants.FieldName:                        "WidgetType",
		constants.FieldSysRecordType_DeveloperName: "WidgetType",
	}}
	resolver := NewValidationFormulaRelationResolver(metadataSvc, repo)
	related, err := resolver(t.Context(), "Widget", models.SObject{
		constants.FieldRecordTypeID: "record-type-widget",
	}, constants.ObjectRecordType)
	if err != nil || related[constants.FieldName] != "WidgetType" {
		t.Fatalf("legacy resolver = %#v, %v", related, err)
	}
	if repo.findOneCalls != 1 {
		t.Fatalf("legacy physical RecordType reads = %d, want 1", repo.findOneCalls)
	}
}

func TestValidateRecordRecordTypeFlatDisplayUsesCanonicalDeveloperName(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	recordTypeName := "Contract Only (APAC)"
	recordTypeDeveloperName := utils.SanitizeDeveloperName(recordTypeName)
	ownerPack := "salesforce-crm-standard"
	recordTypeRelationship := constants.ObjectRecordType
	schema := &models.ObjectMetadata{
		APIName: "Order",
		Fields: []models.FieldMetadata{
			{
				APIName:          constants.FieldRecordTypeID,
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{constants.ObjectRecordType},
				RelationshipName: &recordTypeRelationship,
			},
		},
	}
	metadataSvc := newValidationTestMetadataService(t,
		schema,
		recordTypeValidationPseudoSchema(),
	)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			record: models.SObject{
				constants.FieldID:   "012000000000001AAA",
				constants.FieldName: recordTypeName,
				"DeveloperName":     recordTypeDeveloperName,
			},
		},
	))

	record := models.SObject{
		constants.FieldRecordTypeID:           "012000000000001AAA",
		constants.FieldRecordTypeID + "_Name": recordTypeName,
	}
	rules := []*models.ValidationRule{{
		Name:         "FlatDisplayRecordTypeDeveloperName",
		Active:       true,
		Condition:    `RecordType.DeveloperName == "Contract_Only_APAC"`,
		ErrorMessage: "record type developer name matched stored value despite flat display shortcut",
		OwnerPack:    &ownerPack,
	}}

	err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil)
	if err == nil || !strings.Contains(err.Error(), "record type developer name matched stored value despite flat display shortcut") {
		t.Fatalf("ValidateRecord() error = %v, want validation failure proving RecordType flat display still fetches stored DeveloperName", err)
	}
}

func TestValidateRecordDoesNotSynthesizeDeveloperNameForNilStoredRecordTypeDeveloperName(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	recordTypeName := "Contract Only (APAC)"
	recordTypeRelationship := constants.ObjectRecordType
	schema := &models.ObjectMetadata{
		APIName: "Order",
		Fields: []models.FieldMetadata{
			{
				APIName:          constants.FieldRecordTypeID,
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{constants.ObjectRecordType},
				RelationshipName: &recordTypeRelationship,
			},
		},
	}
	metadataSvc := newValidationTestMetadataService(t,
		schema,
		recordTypeValidationPseudoSchema(),
	)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			record: models.SObject{
				constants.FieldID:   "012000000000001AAA",
				constants.FieldName: recordTypeName,
				"DeveloperName":     nil,
			},
		},
	))

	record := models.SObject{constants.FieldRecordTypeID: "012000000000001AAA"}
	rules := []*models.ValidationRule{{
		Name:         "NilStoredRecordTypeDeveloperNameStaysNonCanonical",
		Active:       true,
		Condition:    `RecordType.DeveloperName == "Contract_Only_APAC"`,
		ErrorMessage: "record type developer name should stay blank when stored value is blank",
	}}

	err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil)
	if err != nil {
		t.Fatalf("ValidateRecord() error = %v, want nil when stored DeveloperName is blank", err)
	}
}

func TestValidateRecordTenantRuleKeepsLegacyRecordTypeDeveloperNameSemantics(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	recordTypeName := "Contract Only (APAC)"
	recordTypeDeveloperName := utils.SanitizeDeveloperName(recordTypeName)
	recordTypeRelationship := constants.ObjectRecordType
	schema := &models.ObjectMetadata{
		APIName: "Order",
		Fields: []models.FieldMetadata{
			{
				APIName:          constants.FieldRecordTypeID,
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{constants.ObjectRecordType},
				RelationshipName: &recordTypeRelationship,
			},
		},
	}
	metadataSvc := newValidationTestMetadataService(t,
		schema,
		recordTypeValidationPseudoSchema(),
	)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			record: models.SObject{
				constants.FieldID:   "012000000000001AAA",
				constants.FieldName: recordTypeName,
				"DeveloperName":     recordTypeDeveloperName,
			},
		},
	))

	record := models.SObject{constants.FieldRecordTypeID: "012000000000001AAA"}
	rules := []*models.ValidationRule{{
		Name:         "TenantRuleRecordTypeDeveloperNameCompatibility",
		Active:       true,
		Condition:    `RecordType.DeveloperName == "Contract Only (APAC)"`,
		ErrorMessage: "tenant rule should keep pre-cutover RecordType.DeveloperName semantics",
		IsCustomized: true,
	}}

	err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil)
	if err == nil || !strings.Contains(err.Error(), "tenant rule should keep pre-cutover RecordType.DeveloperName semantics") {
		t.Fatalf("ValidateRecord() error = %v, want validation failure proving tenant rule still sees label-shaped RecordType.DeveloperName", err)
	}
}

func TestValidateRecordCustomizedPackOwnedRuleKeepsLegacyRecordTypeDeveloperNameSemantics(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	recordTypeName := "Contract Only (APAC)"
	recordTypeDeveloperName := utils.SanitizeDeveloperName(recordTypeName)
	ownerPack := "salesforce-crm-standard"
	recordTypeRelationship := constants.ObjectRecordType
	schema := &models.ObjectMetadata{
		APIName: "Order",
		Fields: []models.FieldMetadata{
			{
				APIName:          constants.FieldRecordTypeID,
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{constants.ObjectRecordType},
				RelationshipName: &recordTypeRelationship,
			},
		},
	}
	metadataSvc := newValidationTestMetadataService(t,
		schema,
		recordTypeValidationPseudoSchema(),
	)
	vs.SetFormulaRelationResolver(NewValidationFormulaRelationResolver(
		metadataSvc,
		validationRecordRepoStub{
			record: models.SObject{
				constants.FieldID:   "012000000000001AAA",
				constants.FieldName: recordTypeName,
				"DeveloperName":     recordTypeDeveloperName,
			},
		},
	))

	record := models.SObject{constants.FieldRecordTypeID: "012000000000001AAA"}
	rules := []*models.ValidationRule{{
		Name:         "CustomizedStandardRuleRecordTypeDeveloperNameCompatibility",
		Active:       true,
		Condition:    `RecordType.DeveloperName == "Contract Only (APAC)"`,
		ErrorMessage: "customized standard rule should keep pre-cutover RecordType.DeveloperName semantics",
		IsCustomized: true,
		OwnerPack:    &ownerPack,
	}}

	err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil)
	if err == nil || !strings.Contains(err.Error(), "customized standard rule should keep pre-cutover RecordType.DeveloperName semantics") {
		t.Fatalf("ValidateRecord() error = %v, want validation failure proving customized standard rule still sees label-shaped RecordType.DeveloperName", err)
	}
}

func TestValidateRecordAllowsSupportedUserGlobals(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetFormulaRelationResolver(func(_ context.Context, _ string, _ models.SObject, _ string) (models.SObject, error) {
		return nil, nil
	})

	schema := validationRuleTestSchema()
	record := models.SObject{"EffectiveDate": "2026-06-01"}
	rules := []*models.ValidationRule{{
		Name:         "SupportedGlobals",
		Active:       true,
		Condition:    `$Profile.Name == "System Administrator" && $UserRole.Name == "CFO"`,
		ErrorMessage: "should not fire",
	}}
	userCtx := map[string]interface{}{
		"__Profile":  map[string]interface{}{"Name": "System Administrator"},
		"__UserRole": map[string]interface{}{"Name": "CEO"},
	}

	if err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, userCtx); err != nil {
		t.Fatalf("ValidateRecord supported user globals error = %v, want nil", err)
	}
}

func TestValidateRecord_InsertSkipsDefaultedOnCreateRequiredField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: constants.TableUser,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldFirstName, Type: constants.FieldTypeString, Required: true, DefaultedOnCreate: true},
			{APIName: constants.FieldLastName, Type: constants.FieldTypeString, Required: true},
		},
	}
	record := models.SObject{
		constants.FieldLastName: "Doe",
	}

	if err := vs.ValidateRecord(context.Background(), record, schema, nil, nil, nil); err != nil {
		t.Fatalf("ValidateRecord() error = %v, want nil when required field is server-defaulted on create", err)
	}
}

func TestValidateRecord_InsertStillRequiresNonDefaultedField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: constants.TableUser,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldFirstName, Type: constants.FieldTypeString, Required: true, DefaultedOnCreate: true},
			{APIName: constants.FieldLastName, Type: constants.FieldTypeString, Required: true},
		},
	}
	record := models.SObject{}

	err := vs.ValidateRecord(context.Background(), record, schema, nil, nil, nil)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure for missing non-defaulted required field")
	}
	if !strings.Contains(err.Error(), constants.FieldLastName) {
		t.Fatalf("ValidateRecord() error = %v, want missing LastName", err)
	}
}

func TestValidateRecord_UpdateRejectsChangedRequiredFieldBlank(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Case",
		Fields: []models.FieldMetadata{
			{APIName: "Resolution__c", Type: constants.FieldTypeString, Required: true},
		},
	}
	oldRecord := models.SObject{"Resolution__c": "fixed"}
	record := models.SObject{"Resolution__c": ""}

	err := vs.ValidateRecord(context.Background(), record, schema, nil, &oldRecord, nil)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure for changed blank required field")
	}
	if !strings.Contains(err.Error(), "Resolution__c") {
		t.Fatalf("ValidateRecord() error = %v, want Resolution__c", err)
	}
}

func TestValidateRecord_UpdateAllowsUnchangedHistoricalNullRequiredField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Case",
		Fields: []models.FieldMetadata{
			{APIName: "Resolution__c", Type: constants.FieldTypeString, Required: true},
			{APIName: "Status", Type: constants.FieldTypeString},
		},
	}
	oldRecord := models.SObject{"Resolution__c": nil, "Status": "New"}
	record := models.SObject{"Resolution__c": nil, "Status": "Closed"}

	if err := vs.ValidateRecord(context.Background(), record, schema, nil, &oldRecord, nil); err != nil {
		t.Fatalf("ValidateRecord() error = %v, want nil for unchanged historical null required field", err)
	}
}

func TestValidateRecordIgnoresQuotedDottedTextInFormula(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetFormulaRelationResolver(func(_ context.Context, _ string, _ models.SObject, _ string) (models.SObject, error) {
		return nil, nil
	})

	schema := validationRuleTestSchema()
	record := models.SObject{"EffectiveDate": "2026-06-01", "Status": "Draft"}
	rules := []*models.ValidationRule{{
		Name:         "QuotedDottedText",
		Active:       true,
		Condition:    `TEXT(Status) == "Contract.StartDate > EffectiveDate"`,
		ErrorMessage: "should not fire",
	}}

	if err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, nil); err != nil {
		t.Fatalf("ValidateRecord quoted dotted text error = %v, want nil", err)
	}
}

func TestValidateRecordFailsClosedOnActiveCompilerOnlyFunctions(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Issue",
		Fields: []models.FieldMetadata{
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}

	tests := []struct {
		name      string
		condition string
	}{
		{name: "ISNUMBER", condition: `ISNUMBER(Subject) == false`},
		{name: "BR", condition: `BR() == "\n"`},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := vs.ValidateRecord(
				context.Background(),
				models.SObject{"Subject": ""},
				schema,
				[]*models.ValidationRule{{
					Name:         tt.name,
					Active:       true,
					Condition:    tt.condition,
					ErrorMessage: "should fail closed",
				}},
				nil,
				nil,
			)
			if err == nil {
				t.Fatalf("ValidateRecord(%q) error = nil, want evaluation failure instead of import-artifact skip", tt.condition)
			}
			if !strings.Contains(err.Error(), "failed to evaluate") {
				t.Fatalf("ValidateRecord(%q) error = %v, want evaluation failure", tt.condition, err)
			}
		})
	}
}

// TestValidateRecordBLANKVALUEEvaluatesCorrectly verifies that BLANKVALUE is
// no longer treated as a known-unsupported function and evaluates correctly
// at validation time once it is registered with the frozen runtime.
func TestValidateRecordBLANKVALUEEvaluatesCorrectly(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Issue",
		Fields: []models.FieldMetadata{
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}

	t.Run("blank field returns default — rule fires", func(t *testing.T) {
		// BLANKVALUE(Subject, "n/a") == "n/a" should be TRUE when Subject is ""
		// meaning the validation rule should fire (return a ValidationRuleError).
		err := vs.ValidateRecord(
			context.Background(),
			models.SObject{"Subject": ""},
			schema,
			[]*models.ValidationRule{{
				Name:         "BLANKVALUE fires on blank",
				Active:       true,
				Condition:    `BLANKVALUE(Subject, "n/a") == "n/a"`,
				ErrorMessage: "subject is blank",
			}},
			nil,
			nil,
		)
		if err == nil {
			t.Fatal("ValidateRecord: error = nil, want ValidationRuleError (rule should fire because Subject is blank)")
		}
		if !strings.Contains(err.Error(), "subject is blank") {
			t.Fatalf("ValidateRecord: error = %v, want message containing 'subject is blank'", err)
		}
	})

	t.Run("non-blank field — rule does not fire", func(t *testing.T) {
		// BLANKVALUE(Subject, "n/a") == "n/a" is FALSE when Subject == "hello"
		err := vs.ValidateRecord(
			context.Background(),
			models.SObject{"Subject": "hello"},
			schema,
			[]*models.ValidationRule{{
				Name:         "BLANKVALUE no-op on filled",
				Active:       true,
				Condition:    `BLANKVALUE(Subject, "n/a") == "n/a"`,
				ErrorMessage: "subject is blank",
			}},
			nil,
			nil,
		)
		if err != nil {
			t.Fatalf("ValidateRecord: error = %v, want nil (rule should not fire because Subject is non-blank)", err)
		}
	})
}

// TestValidateRecordEvaluatesLPADAndRPAD verifies that LPAD and RPAD are now
// supported in the frozen formula runtime: validation rules using them evaluate
// (fire or pass) rather than returning an evaluation error.
//
// Note: string literals containing only whitespace are normalized by
// normalizeFormulaExpression before string-literal extraction, so pad
// characters in test conditions must be non-whitespace to avoid that
// pre-existing normalization. Space-as-pad-character correctness is covered
// in the expression-engine unit tests (TestRPAD_RuntimeEvaluation).
func TestValidateRecordEvaluatesLPADAndRPAD(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Issue",
		Fields: []models.FieldMetadata{
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}

	tests := []struct {
		name      string
		record    models.SObject
		condition string
		wantFire  bool // true = validation rule should fire (validation error)
	}{
		{
			name:      "LPAD fires when padded value matches condition",
			record:    models.SObject{"Subject": ""},
			condition: `LPAD(Subject, 5, "0") == "00000"`,
			wantFire:  true,
		},
		{
			name:      "LPAD does not fire when padded value does not match",
			record:    models.SObject{"Subject": "hello"},
			condition: `LPAD(Subject, 5, "0") == "00000"`,
			wantFire:  false,
		},
		{
			// Use non-whitespace pad to avoid the pre-existing normalizeFormulaExpression
			// whitespace-collapsing that runs before string-literal extraction.
			name:      "RPAD fires when padded value matches condition",
			record:    models.SObject{"Subject": ""},
			condition: `RPAD(Subject, 5, "0") == "00000"`,
			wantFire:  true,
		},
		{
			name:      "RPAD does not fire when padded value does not match",
			record:    models.SObject{"Subject": "hello"},
			condition: `RPAD(Subject, 5, "0") == "00000"`,
			wantFire:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := vs.ValidateRecord(
				context.Background(),
				tt.record,
				schema,
				[]*models.ValidationRule{{
					Name:         tt.name,
					Active:       true,
					Condition:    tt.condition,
					ErrorMessage: "rule fired",
				}},
				nil,
				nil,
			)
			if tt.wantFire {
				if err == nil {
					t.Fatalf("ValidateRecord(%q) error = nil; want validation rule to fire", tt.condition)
				}
				if strings.Contains(err.Error(), "failed to evaluate") {
					t.Fatalf("ValidateRecord(%q) error = %v; want validation rule error, not evaluation failure (LPAD/RPAD should now be implemented)", tt.condition, err)
				}
			} else {
				if err != nil {
					t.Fatalf("ValidateRecord(%q) error = %v; want nil (rule should not fire)", tt.condition, err)
				}
			}
		})
	}
}

func TestIsSalesforceImportArtifactDoesNotClassifyActiveCompilerFunctions(t *testing.T) {
	for _, ident := range []string{
		"BLANKVALUE",
		"ISNUMBER",
		"LPAD",
		"RPAD",
		"IMAGE",
		"BR",
		"DATETIMEVALUE",
	} {
		if isSalesforceImportArtifact(ident) {
			t.Fatalf("isSalesforceImportArtifact(%q) = true, want false", ident)
		}
	}
}

func TestValidationServiceImportArtifactUsesEngineFunctionRegistry(t *testing.T) {
	engine := formula.NewEngine(formula.WithoutRuntimeFunctionBundles())
	if err := engine.RegisterFunctionBundle(formula.FunctionBundle{Functions: []formula.FunctionDefinition{{
		Name:    "VLOOKUP",
		MinArgs: 1,
		MaxArgs: 1,
		Return:  formula.FunctionReturnDefinition{Kind: "fixed", Type: spec.TypeString},
		Args:    []spec.ArgSpec{{Type: spec.TypeAny, Nullable: true}},
		Evaluator: formula.FunctionEvaluator{
			Kind:   formula.FormulaExprV1EvaluatorKind,
			Params: []string{"value"},
			Body:   "TEXT(value)",
		},
	}}}); err != nil {
		t.Fatalf("RegisterFunctionBundle(VLOOKUP): %v", err)
	}

	if !isSalesforceImportArtifact("VLOOKUP") {
		t.Fatal("default import-artifact helper should classify VLOOKUP as an import gap")
	}
	if NewValidationService(engine).isSalesforceImportArtifact("VLOOKUP") {
		t.Fatal("engine-registered VLOOKUP must not be classified as an import gap")
	}
}

// TestValidateRecord_IMAGE_Evaluates confirms IMAGE is supported in the frozen
// formula runtime and validation rules that reference IMAGE are evaluated
// (not silently skipped as import artifacts, and not failing with evaluation error).
func TestValidateRecord_IMAGE_Evaluates(t *testing.T) {
	engine := formula.NewEngine()
	if err := engine.RegisterFunctionBundle(formula.FunctionBundle{
		Functions: []formula.FunctionDefinition{{
			Name:    "IMAGE",
			MinArgs: 2,
			MaxArgs: 4,
			Return:  formula.FunctionReturnDefinition{Kind: "fixed", Type: spec.TypeString},
			Args:    []spec.ArgSpec{{Type: spec.TypeString, Nullable: true}},
			Evaluator: formula.FunctionEvaluator{
				Kind:   formula.FormulaExprV1EvaluatorKind,
				Params: []string{"arg0", "arg1"},
				Body:   "arg0",
			},
		}},
	}); err != nil {
		t.Fatalf("RegisterFunctionBundle IMAGE: %v", err)
	}
	vs := NewValidationService(engine)
	schema := &models.ObjectMetadata{
		APIName: "Issue",
		Fields: []models.FieldMetadata{
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}

	// IMAGE("url", "alt", w, h) == "url" should be TRUE, triggering the rule.
	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{"Subject": "test"},
		schema,
		[]*models.ValidationRule{{
			Name:         "IMAGE_check",
			Active:       true,
			Condition:    `IMAGE("https://example.com/logo.png", "alt", 80, 80) == "https://example.com/logo.png"`,
			ErrorMessage: "IMAGE passthrough triggered",
		}},
		nil,
		nil,
	)
	// The condition evaluates to true → validation error is expected (rule fires).
	// What must NOT happen: silent skip (import artifact) or "failed to evaluate" error.
	if err == nil {
		t.Fatal("ValidateRecord IMAGE rule: error = nil, want validation rule to fire (condition is true)")
	}
	if strings.Contains(err.Error(), "failed to evaluate") {
		t.Fatalf("ValidateRecord IMAGE rule: got evaluation failure %v, want rule to fire cleanly", err)
	}
	if !strings.Contains(err.Error(), "IMAGE passthrough triggered") {
		t.Fatalf("ValidateRecord IMAGE rule: error = %v, want rule error message 'IMAGE passthrough triggered'", err)
	}
}

func TestValidateRecordIncludesRecognizesStoredMultiPicklistJSON(t *testing.T) {
	vs := newValidationServiceWithIncludes(t)
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{
				APIName: "Existing_Database_Deployment__c",
				Type:    constants.FieldTypeMultiPicklist,
				Options: []models.PicklistOption{
					{Value: "Others", Label: "Others"},
				},
			},
			{
				APIName: "Existing_Database_Deployment_Remarks__c",
				Type:    constants.FieldTypeTextarea,
			},
		},
	}
	rules := []*models.ValidationRule{{
		Name:         "Existing_Database_Deployment_Remarks",
		Active:       true,
		Condition:    `AND(INCLUDES(Existing_Database_Deployment__c, "Others"), ISBLANK(Existing_Database_Deployment_Remarks__c))`,
		ErrorMessage: `Please specify which Cloud / Infra Provider in the Remarks field only if "Others" is selected in the Cloud / Infra Provider.`,
	}}

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{
			"Existing_Database_Deployment__c":         `["Others"]`,
			"Existing_Database_Deployment_Remarks__c": nil,
		},
		schema,
		rules,
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure for Others without remarks")
	}
	if !strings.Contains(err.Error(), "Please specify which Cloud / Infra Provider") {
		t.Fatalf("ValidateRecord() error = %v, want downstream remarks validation message", err)
	}
}

func TestValidateRecordExpressesFieldPresenceInvariantsWithValidationRules(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "AccessGrant",
		Fields: []models.FieldMetadata{
			{APIName: "PrimaryTargetId", Type: constants.FieldTypeString},
			{APIName: "SecondaryTargetId", Type: constants.FieldTypeString},
			{APIName: "ScopedTargetId", Type: constants.FieldTypeString},
			{APIName: "ForbiddenTargetId", Type: constants.FieldTypeString},
		},
	}
	rules := []*models.ValidationRule{
		{
			Name:   "Target_Exactly_One",
			Active: true,
			Condition: `OR(
				AND(ISBLANK(PrimaryTargetId), ISBLANK(SecondaryTargetId)),
				AND(NOT(ISBLANK(PrimaryTargetId)), NOT(ISBLANK(SecondaryTargetId)))
			)`,
			ErrorMessage: "set exactly one target",
		},
		{
			Name:         "Scoped_Target_Forbids_Explicit_Target",
			Active:       true,
			Condition:    `AND(NOT(ISBLANK(ScopedTargetId)), NOT(ISBLANK(ForbiddenTargetId)))`,
			ErrorMessage: "scoped target cannot include forbidden target",
		},
	}

	tests := []struct {
		name    string
		record  models.SObject
		wantErr string
	}{
		{
			name:    "exactly one rejects neither target",
			record:  models.SObject{},
			wantErr: "set exactly one target",
		},
		{
			name: "exactly one rejects both targets",
			record: models.SObject{
				"PrimaryTargetId":   "p01",
				"SecondaryTargetId": "s01",
			},
			wantErr: "set exactly one target",
		},
		{
			name: "conditional none rejects forbidden companion",
			record: models.SObject{
				"PrimaryTargetId":   "p01",
				"ScopedTargetId":    "scope01",
				"ForbiddenTargetId": "forbid01",
			},
			wantErr: "scoped target cannot include forbidden target",
		},
		{
			name: "exactly one accepts primary target only",
			record: models.SObject{
				"PrimaryTargetId": "p01",
			},
		},
		{
			name: "conditional none accepts scoped target without forbidden companion",
			record: models.SObject{
				"PrimaryTargetId": "p01",
				"ScopedTargetId":  "scope01",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := vs.ValidateRecord(context.Background(), tt.record, schema, rules, nil, nil)
			if tt.wantErr == "" {
				if err != nil {
					t.Fatalf("ValidateRecord() error = %v, want nil", err)
				}
				return
			}
			if err == nil {
				t.Fatalf("ValidateRecord() error = nil, want %q", tt.wantErr)
			}
			if !strings.Contains(err.Error(), tt.wantErr) {
				t.Fatalf("ValidateRecord() error = %v, want %q", err, tt.wantErr)
			}
		})
	}
}

func TestValidateRecordOptionlessMultiPicklistKeepsJSONStringLiteralForIncludes(t *testing.T) {
	vs := newValidationServiceWithIncludes(t)
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{
				APIName: "Existing_Database_Deployment__c",
				Type:    constants.FieldTypeMultiPicklist,
			},
		},
	}
	rules := []*models.ValidationRule{{
		Name:         "JSONStringLiteralIncludes",
		Active:       true,
		Condition:    `INCLUDES(Existing_Database_Deployment__c, "Others")`,
		ErrorMessage: "should stay literal",
	}}

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{
			"Existing_Database_Deployment__c": `["Others"]`,
		},
		schema,
		rules,
		nil,
		nil,
	)
	if err != nil {
		t.Fatalf("ValidateRecord() error = %v, want nil when optionless JSON-looking literal stays scalar", err)
	}
}

func TestValidateRecordOptionlessMultiPicklistKeepsJSONStringLiteralForPriorValue(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{
				APIName: "Existing_Database_Deployment__c",
				Type:    constants.FieldTypeMultiPicklist,
			},
		},
	}
	rules := []*models.ValidationRule{{
		Name:         "JSONStringLiteralPriorValue",
		Active:       true,
		Condition:    `AND(ISCHANGED(Existing_Database_Deployment__c), PRIORVALUE(Existing_Database_Deployment__c) == "[\"Others\"]")`,
		ErrorMessage: "prior literal matched",
	}}
	oldRecord := models.SObject{
		"Existing_Database_Deployment__c": `["Others"]`,
	}

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{
			"Existing_Database_Deployment__c": "Different",
		},
		schema,
		rules,
		&oldRecord,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure when PRIORVALUE keeps JSON-looking literal")
	}
	if !strings.Contains(err.Error(), "prior literal matched") {
		t.Fatalf("ValidateRecord() error = %v, want prior literal validation message", err)
	}
}

func TestValidateRecordOptionlessMultiPicklistRecognizesAuthoritativeStoredShape(t *testing.T) {
	vs := newValidationServiceWithIncludes(t)
	schema := &models.ObjectMetadata{
		APIName: "Opportunity",
		Fields: []models.FieldMetadata{
			{
				APIName: "Existing_Database_Deployment__c",
				Type:    constants.FieldTypeMultiPicklist,
			},
			{
				APIName: "Existing_Database_Deployment_Remarks__c",
				Type:    constants.FieldTypeTextarea,
			},
		},
	}
	rules := []*models.ValidationRule{{
		Name:         "OptionlessStoredOthers",
		Active:       true,
		Condition:    `AND(INCLUDES(Existing_Database_Deployment__c, "Others"), ISBLANK(Existing_Database_Deployment_Remarks__c))`,
		ErrorMessage: "stored others matched",
	}}

	err := vs.ValidateRecord(
		context.Background(),
		models.SObject{
			"Existing_Database_Deployment__c":         schemautil.NormalizedMultiPicklistJSON(`["Others"]`),
			"Existing_Database_Deployment_Remarks__c": nil,
		},
		schema,
		rules,
		nil,
		nil,
	)
	if err == nil {
		t.Fatal("ValidateRecord() error = nil, want validation failure for authoritative stored optionless shape")
	}
	if !strings.Contains(err.Error(), "stored others matched") {
		t.Fatalf("ValidateRecord() error = %v, want authoritative stored-shape validation message", err)
	}
}

func TestValidateFieldMetadata_RejectsFormulaOnNonFormulaType(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())

	cases := []struct {
		name        string
		fieldType   constants.SchemaFieldType
		formula     string
		formulaMode string // empty string ⇒ nil pointer
		returnType  string // required when fieldType==formula (validation_service.go:616-622)
		wantErr     bool
	}{
		// Dead-column case (blank formulaMode): no compute path is wired,
		// reject — this is the bug #958 targets.
		{name: "string_with_formula", fieldType: constants.FieldTypeString, formula: "Amount * 2", wantErr: true},
		{name: "number_with_formula", fieldType: constants.FieldTypeDouble, formula: "1 + 1", wantErr: true},
		{name: "date_with_formula", fieldType: constants.FieldTypeDate, formula: "TODAY()", wantErr: true},
		// Frozen on non-formula type: formula_compute.go:162,282 requires
		// Type==formula, so the save-time compute never fires — reject.
		{name: "string_with_formula_frozen_mode", fieldType: constants.FieldTypeString, formula: "Amount * 2", formulaMode: "frozen", wantErr: true},
		// Active on non-formula type at the METADATA API path: REJECTED
		// (Codex round-5 BLOCK on #964). The pack apply path (validated in
		// pack_field_shapes.go) PERMITS this shape because the install
		// pipeline provisions the FormulaViewService substrate end-to-end.
		// But CreateField/batch DDL and the synchronous rebuild/rollback
		// path that back the metadata API only key off Type==formula, so a
		// metadata-API create of a scalar+active computed field returns
		// success while the physical column / view dependency / rollback
		// hooks all key on the wrong condition. Until those code paths
		// adopt the isActiveComputedField contract, the metadata API fails
		// closed on this shape — this is the asymmetry called out in the
		// validation_service.go comment.
		{name: "string_with_formula_active_mode_metadata_api_rejected", fieldType: constants.FieldTypeString, formula: "Amount * 2", formulaMode: "active", wantErr: true},
		{name: "currency_with_formula_active_mode_metadata_api_rejected", fieldType: constants.FieldTypeCurrency, formula: "Quantity * UnitPrice", formulaMode: "active", wantErr: true},
		// Case-folded / padded / arbitrary spellings of "active" are REJECTED
		// (Codex round-3 BLOCK on #964): downstream source-backed-pack readers
		// (source_pack_ops.go:1186, schema_pack_owned_field.go:597) compare
		// the canonical lowercase form exactly. Accepting "ACTIVE" here
		// would let a pack install bypass the gate while still missing every
		// downstream match — yielding a dead column with no backing storage
		// and no derived view.
		{name: "string_with_formula_ACTIVE_uppercase_rejected", fieldType: constants.FieldTypeString, formula: "Amount * 2", formulaMode: "ACTIVE", wantErr: true},
		{name: "string_with_formula_Active_titlecase_rejected", fieldType: constants.FieldTypeString, formula: "Amount * 2", formulaMode: "Active", wantErr: true},
		{name: "string_with_formula_padded_active_rejected", fieldType: constants.FieldTypeString, formula: "Amount * 2", formulaMode: " active ", wantErr: true},
		{name: "string_with_formula_random_mode_rejected", fieldType: constants.FieldTypeString, formula: "Amount * 2", formulaMode: "random_mode", wantErr: true},
		// Golden formula-type cases: include ReturnType to satisfy the
		// upstream Type==formula validity rule at validation_service.go
		// :616-622 — exercising the new guard, not the orthogonal one.
		{name: "formula_with_expression", fieldType: constants.FieldTypeFormula, formula: "Amount * 2", returnType: "number", wantErr: false},
		{name: "string_nil_formula", fieldType: constants.FieldTypeString, formula: "", wantErr: false},
		{name: "string_whitespace_formula", fieldType: constants.FieldTypeString, formula: "   ", wantErr: false},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			f := tc.formula
			field := &models.FieldMetadata{
				APIName: "Test__c",
				Type:    tc.fieldType,
				Formula: &f,
			}
			if tc.formulaMode != "" {
				mode := tc.formulaMode
				field.FormulaMode = &mode
			}
			if tc.returnType != "" {
				rt := models.FieldType(tc.returnType)
				field.ReturnType = &rt
			}
			err := vs.ValidateFieldMetadata(field)
			if tc.wantErr && err == nil {
				t.Fatal("ValidateFieldMetadata = nil, want error for formula on non-formula type")
			}
			if !tc.wantErr && err != nil {
				t.Fatalf("ValidateFieldMetadata = %v, want nil", err)
			}
			if tc.wantErr && err != nil {
				msg := err.Error()
				if strings.Contains(msg, "is not a canonical value") {
					return
				}
				for _, want := range []string{
					"field.formula.expression_on_non_formula_type",
					`field "Test__c"`,
					`expected type "formula"`,
				} {
					if !strings.Contains(msg, want) {
						t.Fatalf("ValidateFieldMetadata = %v, want %q in type-coherence rejection", err, want)
					}
				}
			}
		})
	}
}

// TestValidateFieldMetadata_FormulaTypeWithNonCanonicalFormulaMode covers
// Codex round-4 BLOCK on #964. The round-3 fix scoped the canonical-
// formulaMode check to the `field.Type != formula` branch, so create/update
// paths could persist a formula-typed field with FormulaMode "FROZEN" or
// " frozen ". Save-time compute (records/formula_compute.go:175/285,
// persistence_bulk_insert.go:624) matches "frozen" exactly, so a non-
// canonical spelling silently skips compute — the physically-created formula
// column never receives a value. The canonical-mode gate now fires
// regardless of Type and regardless of whether Formula is set.
func TestValidateFieldMetadata_FormulaTypeWithNonCanonicalFormulaMode(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())

	cases := []struct {
		name        string
		formulaMode string
		wantErr     bool
	}{
		// Canonical regressions: must continue to pass.
		{name: "formula_type_active_mode_allowed", formulaMode: "active", wantErr: false},
		{name: "formula_type_frozen_mode_allowed", formulaMode: "frozen", wantErr: false},
		{name: "formula_type_blank_mode_allowed", formulaMode: "", wantErr: false},
		// Non-canonical spellings on a formula-typed field — the round-4 BLOCK.
		{name: "formula_type_ACTIVE_uppercase_rejected", formulaMode: "ACTIVE", wantErr: true},
		{name: "formula_type_padded_active_rejected", formulaMode: " active ", wantErr: true},
		{name: "formula_type_FROZEN_uppercase_rejected", formulaMode: "FROZEN", wantErr: true},
		{name: "formula_type_padded_frozen_rejected", formulaMode: " frozen ", wantErr: true},
		{name: "formula_type_random_mode_rejected", formulaMode: "random_mode", wantErr: true},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			expr := "Amount * 2"
			rt := models.FieldType("number")
			field := &models.FieldMetadata{
				APIName:    "Calc__c",
				Type:       constants.FieldTypeFormula,
				Formula:    &expr,
				ReturnType: &rt,
			}
			if tc.formulaMode != "" {
				mode := tc.formulaMode
				field.FormulaMode = &mode
			}
			err := vs.ValidateFieldMetadata(field)
			if tc.wantErr && err == nil {
				t.Fatal("ValidateFieldMetadata = nil, want error for non-canonical formulaMode on formula-typed field")
			}
			if !tc.wantErr && err != nil {
				t.Fatalf("ValidateFieldMetadata = %v, want nil", err)
			}
			if tc.wantErr && err != nil {
				if !strings.Contains(err.Error(), "is not a canonical value") {
					t.Fatalf("ValidateFieldMetadata = %v, want unknown-formulaMode error", err)
				}
			}
		})
	}
}

// TestValidateFieldMetadata_MetadataAPIRejectsScalarActiveComputedField pins
// the metadata-API-vs-pack-apply asymmetry called out in Codex round-5 on
// PR #964. The pack apply path (pack_field_shapes.go) admits
// non-formula Type + non-empty Formula + formulaMode "active" because the
// install pipeline provisions the FormulaViewService substrate end-to-end.
// The metadata API path (CreateField / batch DDL / synchronous rebuild /
// rollback) only keys off Type==formula and does NOT provision the
// view-time substrate, so this shape would return success while
// FormulaViewService.ResolveTableName routes reads to a missing v_<object>
// and fails closed. The metadata API therefore rejects the shape until the
// underlying paths adopt the isActiveComputedField contract.
func TestValidateFieldMetadata_MetadataAPIRejectsScalarActiveComputedField(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())

	expr := "Amount * 2"
	mode := "active"
	field := &models.FieldMetadata{
		APIName:     "Calc__c",
		Type:        constants.FieldTypeString,
		Formula:     &expr,
		FormulaMode: &mode,
	}
	err := vs.ValidateFieldMetadata(field)
	if err == nil {
		t.Fatal("ValidateFieldMetadata = nil, want rejection of scalar+active computed field at metadata-API boundary")
	}
	msg := err.Error()
	for _, want := range []string{
		"field.formula.expression_on_non_formula_type",
		`field "Calc__c"`,
		`type "string"`,
		`expected type "formula"`,
	} {
		if !strings.Contains(msg, want) {
			t.Fatalf("ValidateFieldMetadata error = %v, want %q in stable type-coherence rejection", err, want)
		}
	}
}

func TestValidateFieldMetadata_UsesSharedFormulaTypeErrorHelper(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())

	expr := "Amount * 2"
	mode := "active"
	field := &models.FieldMetadata{
		APIName:     "Calc__c",
		Type:        constants.FieldTypeString,
		Formula:     &expr,
		FormulaMode: &mode,
	}

	err := vs.ValidateFieldMetadata(field)
	if err == nil {
		t.Fatal("ValidateFieldMetadata = nil, want shared helper rejection")
	}

	wantErr := FormulaExpressionOnNonFormulaTypeError("", field.APIName, field.Type)
	if err.Error() != wantErr.Error() {
		t.Fatalf("ValidateFieldMetadata error = %q, want shared helper error %q", err.Error(), wantErr.Error())
	}
}

func TestValidateFieldMetadataForUpdateLegacyPackOwnedPresentationEditOnly(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())

	expr := "Amount * 2"
	mode := "active"
	ownerPack := "salesforce-crm-standard"
	existingPackOwned := &models.FieldMetadata{
		APIName:     "Calc__c",
		Label:       "Calc",
		Type:        constants.FieldTypeString,
		Formula:     &expr,
		FormulaMode: &mode,
		OwnerKind:   constants.FieldOwnerKindPack,
		OwnerPack:   &ownerPack,
	}

	t.Run("pack_owned_label_only_edit_allowed", func(t *testing.T) {
		updated := *existingPackOwned
		updated.Label = "Display Calc"

		if err := vs.ValidateFieldMetadataForUpdate(&updated, existingPackOwned); err != nil {
			t.Fatalf("ValidateFieldMetadataForUpdate(pack-owned label-only) = %v, want nil", err)
		}
	})

	t.Run("pack_owned_track_history_only_edit_rejected", func(t *testing.T) {
		updated := *existingPackOwned
		updated.TrackHistory = true

		err := vs.ValidateFieldMetadataForUpdate(&updated, existingPackOwned)
		if err == nil {
			t.Fatal("ValidateFieldMetadataForUpdate(pack-owned trackHistory-only) = nil, want presentation-only rejection")
		}
		if !strings.Contains(err.Error(), "only presentation-only metadata edits (label, help text) are allowed via UpdateField") {
			t.Fatalf("ValidateFieldMetadataForUpdate error = %q, want presentation-only rejection", err.Error())
		}
	})

	t.Run("non_pack_owned_label_only_edit_rejected_by_shared_helper", func(t *testing.T) {
		existing := *existingPackOwned
		existing.OwnerKind = ""
		existing.OwnerPack = nil

		updated := existing
		updated.Label = "Display Calc"

		err := vs.ValidateFieldMetadataForUpdate(&updated, &existing)
		if err == nil {
			t.Fatal("ValidateFieldMetadataForUpdate(non-pack-owned label-only) = nil, want shared formula/type rejection")
		}

		wantErr := FormulaExpressionOnNonFormulaTypeError("", updated.APIName, updated.Type)
		if err.Error() != wantErr.Error() {
			t.Fatalf("ValidateFieldMetadataForUpdate error = %q, want shared helper error %q", err.Error(), wantErr.Error())
		}
	})

	t.Run("pack_owner_kind_without_owner_pack_still_rejected_by_shared_helper", func(t *testing.T) {
		existing := *existingPackOwned
		blankOwnerPack := "   "
		existing.OwnerPack = &blankOwnerPack

		updated := existing
		updated.Label = "Display Calc"

		err := vs.ValidateFieldMetadataForUpdate(&updated, &existing)
		if err == nil {
			t.Fatal("ValidateFieldMetadataForUpdate(pack owner kind without ownerPack) = nil, want shared formula/type rejection")
		}

		wantErr := FormulaExpressionOnNonFormulaTypeError("", updated.APIName, updated.Type)
		if err.Error() != wantErr.Error() {
			t.Fatalf("ValidateFieldMetadataForUpdate error = %q, want shared helper error %q", err.Error(), wantErr.Error())
		}
	})
}

func TestValidateFieldMetadataForUpdateRejectsNonActiveFormulaStorageMutation(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())

	makeField := func(mode, storageMode *string) *models.FieldMetadata {
		expr := "Amount__c * 2"
		returnType := constants.FieldTypeString
		field := &models.FieldMetadata{
			APIName:     "Score__c",
			Type:        constants.FieldTypeFormula,
			Formula:     &expr,
			FormulaMode: mode,
			ReturnType:  &returnType,
		}
		if storageMode != nil {
			field.FormulaStorageMode = storageMode
		}
		return field
	}

	newFormula := "Amount__c * 99"
	frozenMode := "frozen"
	activeMode := "active"
	legacyBlankMode := ""
	writeMaterialized := "write_materialized"
	newReturnType := constants.FieldTypeDouble

	cases := []struct {
		name     string
		existing *models.FieldMetadata
		mutate   func(updated *models.FieldMetadata)
	}{
		{
			name:     "frozen_formula_expression_edit_rejected",
			existing: makeField(&frozenMode, nil),
			mutate: func(updated *models.FieldMetadata) {
				updated.Formula = &newFormula
			},
		},
		{
			name:     "active_to_frozen_mode_switch_rejected",
			existing: makeField(&activeMode, nil),
			mutate: func(updated *models.FieldMetadata) {
				updated.FormulaMode = &frozenMode
			},
		},
		{
			name:     "legacy_nil_formula_mode_formula_edit_rejected",
			existing: makeField(nil, nil),
			mutate: func(updated *models.FieldMetadata) {
				updated.Formula = &newFormula
			},
		},
		{
			name:     "legacy_blank_formula_mode_return_type_edit_rejected",
			existing: makeField(&legacyBlankMode, nil),
			mutate: func(updated *models.FieldMetadata) {
				updated.ReturnType = &newReturnType
			},
		},
		{
			name:     "active_view_only_to_write_materialized_storage_switch_rejected",
			existing: makeField(&activeMode, nil),
			mutate: func(updated *models.FieldMetadata) {
				updated.FormulaStorageMode = &writeMaterialized
			},
		},
		{
			name:     "active_write_materialized_to_view_only_storage_switch_rejected",
			existing: makeField(&activeMode, &writeMaterialized),
			mutate: func(updated *models.FieldMetadata) {
				updated.FormulaStorageMode = nil
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			updated := *tc.existing
			tc.mutate(&updated)

			err := vs.ValidateFieldMetadataForUpdate(&updated, tc.existing)
			if err == nil {
				t.Fatal("ValidateFieldMetadataForUpdate = nil, want non-active formula storage rejection")
			}
			if !strings.Contains(err.Error(), "editing formula expression, mode, storage mode, or return type for frozen/legacy formula fields is not supported via this path") {
				t.Fatalf("ValidateFieldMetadataForUpdate error = %q, want non-active formula storage rejection", err.Error())
			}
		})
	}
}

func TestValidateFieldMetadataRejectsInvalidFormulaStorageMode(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	formulaExpr := "Name"
	formulaMode := "active"
	returnType := constants.FieldTypeString
	invalidStorageMode := "WRITE_MATERIALIZED"

	field := &models.FieldMetadata{
		APIName:            "NameStored",
		Type:               constants.FieldTypeFormula,
		Formula:            &formulaExpr,
		FormulaMode:        &formulaMode,
		FormulaStorageMode: &invalidStorageMode,
		ReturnType:         &returnType,
	}

	err := vs.ValidateFieldMetadata(field)
	if err == nil {
		t.Fatal("ValidateFieldMetadata = nil, want invalid formulaStorageMode rejection")
	}
	if !strings.Contains(err.Error(), "formulaStorageMode") {
		t.Fatalf("ValidateFieldMetadata error = %q, want formulaStorageMode rejection", err.Error())
	}
}

func TestValidateFieldMetadataForUpdateRejectsInvalidFormulaStorageMode(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	formulaExpr := "Name"
	formulaMode := "active"
	returnType := constants.FieldTypeString
	invalidStorageMode := "view-only"

	existing := &models.FieldMetadata{
		APIName:     "NameStored",
		Type:        constants.FieldTypeFormula,
		Formula:     &formulaExpr,
		FormulaMode: &formulaMode,
		ReturnType:  &returnType,
	}
	updated := *existing
	updated.FormulaStorageMode = &invalidStorageMode

	err := vs.ValidateFieldMetadataForUpdate(&updated, existing)
	if err == nil {
		t.Fatal("ValidateFieldMetadataForUpdate = nil, want invalid formulaStorageMode rejection")
	}
	if !strings.Contains(err.Error(), "formulaStorageMode") {
		t.Fatalf("ValidateFieldMetadataForUpdate error = %q, want formulaStorageMode rejection", err.Error())
	}
}

// TestValidateFieldMetadata_NilFormulaFieldDoesNotPanic verifies that a field
// with a nil Formula pointer passes the new guard without panicking.
func TestValidateFieldMetadata_NilFormulaFieldDoesNotPanic(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())

	field := &models.FieldMetadata{
		APIName: "Test__c",
		Type:    constants.FieldTypeString,
		Formula: nil,
	}
	if err := vs.ValidateFieldMetadata(field); err != nil {
		t.Fatalf("ValidateFieldMetadata = %v, want nil", err)
	}
}

// TestWithFormulaPermissionContext_SuperUser verifies that a super-user
// capability holder receives an empty __Permission map from
// withFormulaPermissionContext. Per-rule injection of $Permission.X keys
// happens in ValidateRecord (via extractPermissionKeys), not here. This test
// confirms no hardcoded catalog is used — the map is empty until a rule
// references a specific permission. Regression for issue #609.
func TestWithFormulaPermissionContext_SuperUser(t *testing.T) {
	// Arrange: userContext built from a capability-bearing session that does
	// not depend on the CRM system_admin profile name.
	userContext := map[string]interface{}{
		"__User": map[string]interface{}{
			"ProfileId": "jira_admin",
		},
		constants.ContextKeySystemPermissions: map[string]bool{
			string(constants.SuperUserCapability): true,
		},
		constants.ContextKeyCustomPermissions: map[string]bool{},
	}

	// Act
	out := withFormulaPermissionContext(userContext)

	// Assert: __Permission must be a map[string]bool (empty — no catalog hardcoded).
	// Per-rule keys are injected by ValidateRecord via extractPermissionKeys.
	perms, ok := out["__Permission"].(map[string]bool)
	if !ok {
		t.Fatalf("__Permission is %T, want map[string]bool", out["__Permission"])
	}
	// The map must be empty: no hardcoded permission names exist in L0 code.
	if len(perms) != 0 {
		t.Errorf("__Permission has %d entries for super-user at withFormulaPermissionContext level, want 0 (injected per-rule by ValidateRecord)", len(perms))
	}
}

// TestWithFormulaPermissionContext_SuperUser_EndToEnd verifies that a
// validation rule gating on $Permission.X does not fire for a super-user,
// even when the permission map passed in is empty (PermissionLoader bypass).
func TestWithFormulaPermissionContext_SuperUser_EndToEnd(t *testing.T) {
	vs := NewValidationService(formula.NewEngine())
	vs.SetFormulaRelationResolver(func(_ context.Context, _ string, _ models.SObject, _ string) (models.SObject, error) {
		return nil, nil
	})

	schema := &models.ObjectMetadata{
		APIName: "Order",
		Fields:  []models.FieldMetadata{{APIName: "Status", Type: constants.FieldTypeString}},
	}
	record := models.SObject{"Status": "Active"}

	// Rule fires only when a custom permission is missing. Uses a generic name
	// (CustomGatePermission) that is not in the L0 platform catalog, verifying
	// that the super-user injection path in ValidateRecord dynamically grants
	// any $Permission.X reference without hardcoding L1 CRM names.
	rules := []*models.ValidationRule{{
		Name:         "RequiresCustomGatePermission",
		Active:       true,
		Condition:    `NOT($Permission.CustomGatePermission)`,
		ErrorMessage: "missing CustomGatePermission permission",
	}}

	// userContext simulates a super-user capability holder without relying on
	// the Salesforce CRM system_admin profile name.
	userContext := map[string]interface{}{
		"__User": map[string]interface{}{
			"ProfileId": "jira_admin",
		},
		constants.ContextKeySystemPermissions: map[string]bool{
			string(constants.SuperUserCapability): true,
		},
		constants.ContextKeyCustomPermissions: map[string]bool{},
	}

	err := vs.ValidateRecord(context.Background(), record, schema, rules, nil, userContext)
	if err != nil {
		t.Fatalf("ValidateRecord with super-user returned error %v; want nil (capability bypasses validation $Permission gate)", err)
	}
}

// TestWithFormulaPermissionContext_NonSuperUser verifies that a regular user
// gets the actual permissions from the context maps (no all-true override).
func TestWithFormulaPermissionContext_NonSuperUser(t *testing.T) {
	userContext := map[string]interface{}{
		"__User": map[string]interface{}{
			"ProfileId": constants.ProfileStandardUser,
		},
		constants.ContextKeySystemPermissions: map[string]bool{"RunFlow": true},
		constants.ContextKeyCustomPermissions: map[string]bool{},
	}

	out := withFormulaPermissionContext(userContext)

	perms, ok := out["__Permission"].(map[string]bool)
	if !ok {
		t.Fatalf("__Permission is %T, want map[string]bool", out["__Permission"])
	}
	if !perms["RunFlow"] {
		t.Error("__Permission[RunFlow] = false for standard user with RunFlow granted, want true")
	}
	if perms["ActivateOrder"] {
		t.Error("__Permission[ActivateOrder] = true for standard user without that grant, want false")
	}
}

func validationRuleTestSchema() *models.ObjectMetadata {
	contractRel := "Contract"
	return &models.ObjectMetadata{
		APIName: "Order",
		Fields: []models.FieldMetadata{
			{
				APIName: "EffectiveDate",
				Type:    constants.FieldTypeDate,
			},
			{
				APIName:          "ContractId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"Contract"},
				RelationshipName: &contractRel,
			},
		},
	}
}

func validationFieldTypePtr(v constants.SchemaFieldType) *models.FieldType {
	fieldType := models.FieldType(v)
	return &fieldType
}

func validationStringPtr(v string) *string {
	return &v
}

func newValidationTestMetadataService(t *testing.T, schemas ...*models.ObjectMetadata) *MetadataService {
	t.Helper()
	service := NewMetadataService(&validationTestMetadataRepo{schemas: schemas}, nil, nil)
	if err := service.RefreshCache(context.Background()); err != nil {
		t.Fatalf("RefreshCache() error = %v", err)
	}
	return service
}

type validationTestMetadataRepo struct {
	ports.MetadataRepository
	schemas []*models.ObjectMetadata
}

func (r *validationTestMetadataRepo) GetAllSchemas(ctx context.Context) ([]*models.ObjectMetadata, error) {
	return r.schemas, nil
}

func (r *validationTestMetadataRepo) GetSchemaByAPIName(ctx context.Context, apiName string) (*models.ObjectMetadata, error) {
	for _, schema := range r.schemas {
		if schema != nil && strings.EqualFold(schema.APIName, apiName) {
			return schema, nil
		}
	}
	return nil, nil
}

func (r *validationTestMetadataRepo) GetAllRelationships(ctx context.Context) ([]*models.Relationship, error) {
	return nil, nil
}

func (r *validationTestMetadataRepo) GetAllFlowDefinitions(ctx context.Context) ([]*models.FlowDefinition, error) {
	return nil, nil
}

func (r *validationTestMetadataRepo) GetAllValidationRules(ctx context.Context) ([]*models.ValidationRule, error) {
	return nil, nil
}

type validationRecordRepoStub struct {
	ports.RecordRepository
	record  models.SObject
	records map[string]models.SObject
}

func (r validationRecordRepoStub) FindOne(ctx context.Context, tableName string, id string) (models.SObject, error) {
	if r.records != nil {
		if record, ok := r.records[tableName+"/"+id]; ok {
			return record, nil
		}
	}
	return r.record, nil
}

type validationCountingRecordRepoStub struct {
	ports.RecordRepository
	findOneCalls int
	record       models.SObject
}

func (r *validationCountingRecordRepoStub) FindOne(context.Context, string, string) (models.SObject, error) {
	r.findOneCalls++
	return r.record, nil
}

func setValidationTestLegacyActiveFormulaResolver(vs *ValidationService) {
	vs.SetActiveFormulaResolver(func(ctx context.Context, schema *models.ObjectMetadata, record models.SObject, field *models.FieldMetadata) (interface{}, error) {
		if vs == nil || vs.formula == nil || schema == nil || field == nil || field.Formula == nil {
			return nil, nil
		}

		fetcher := func(current map[string]interface{}, relationName string) (map[string]interface{}, error) {
			if validationFormulaGlobalRoot(relationName) {
				return nil, nil
			}
			if len(current) == 0 {
				return nil, nil
			}

			currentObjectAPIName := schema.APIName
			if objectAPIName := models.RecordObjectType(current); strings.TrimSpace(objectAPIName) != "" {
				currentObjectAPIName = objectAPIName
			}
			if currentObjectAPIName == schema.APIName && !validationKnownFormulaRoot(schema, relationName) {
				return nil, fmt.Errorf("unknown active formula relation %q", relationName)
			}
			related, err := vs.relationResolve(ctx, currentObjectAPIName, models.SObject(current), relationName)
			if related == nil || err != nil {
				return nil, err
			}
			return map[string]interface{}(related), nil
		}

		result, err := vs.formula.Evaluate(strings.TrimSpace(*field.Formula), &formula.Context{
			Record:  map[string]interface{}(record),
			Schema:  schema,
			Fetcher: fetcher,
		})
		if err != nil {
			return nil, err
		}

		if field.ReturnType != nil && constants.NormalizeFieldType(string(*field.ReturnType)) == constants.FieldTypePercent {
			switch v := result.(type) {
			case float64:
				return v * 100.0, nil
			case int:
				return float64(v) * 100.0, nil
			case int64:
				return float64(v) * 100.0, nil
			}
		}
		return result, nil
	})
}

type validationQueryExecutorStub struct {
	rows       []models.SObject
	scripted   [][]models.SObject
	handler    func(sql string, params []interface{}) ([]models.SObject, error)
	err        error
	lastSQL    string
	lastParams []interface{}
	calls      []validationQueryCall
}

func (s *validationQueryExecutorStub) ExecuteRawSQL(ctx context.Context, sql string, params []interface{}) ([]models.SObject, error) {
	s.lastSQL = sql
	s.lastParams = append([]interface{}(nil), params...)
	s.calls = append(s.calls, validationQueryCall{
		sql:    sql,
		params: append([]interface{}(nil), params...),
	})
	if s.err != nil {
		return nil, s.err
	}
	if s.handler != nil {
		return s.handler(sql, params)
	}
	if len(s.scripted) > 0 {
		rows := s.scripted[0]
		s.scripted = s.scripted[1:]
		return rows, nil
	}
	return s.rows, nil
}

type validationQueryCall struct {
	sql    string
	params []interface{}
}

// --- Validation Projection Provider Tests ---

type stubValidationProjectionProvider struct {
	fields map[string][]string
}

func (p *stubValidationProjectionProvider) ValidationProjectionFields(objectName string) ([]string, bool) {
	key := strings.ToLower(objectName)
	fields, ok := p.fields[key]
	return fields, ok
}

func (p *stubValidationProjectionProvider) ValidationProjectionFieldsOrCompute(_ context.Context, objectName string) ([]string, bool) {
	return p.ValidationProjectionFields(objectName)
}

func TestValidationActiveComputedFields_NarrowsWithProvider(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)
	formula := "Id"

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     &formula,
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     &formula,
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaC__c",
				Type:        constants.FieldTypeFormula,
				Formula:     &formula,
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	vs := NewValidationService(nil)
	vs.SetValidationProjectionProvider(&stubValidationProjectionProvider{
		fields: map[string][]string{
			"account": {"FormulaA__c", "FormulaC__c"},
		},
	})

	fields := vs.validationActiveComputedFields(context.Background(), schema)
	if len(fields) != 2 {
		t.Fatalf("expected 2 fields from closure, got %d", len(fields))
	}
	names := map[string]bool{}
	for _, f := range fields {
		names[f.APIName] = true
	}
	if !names["FormulaA__c"] || !names["FormulaC__c"] {
		t.Errorf("expected FormulaA__c and FormulaC__c, got %v", names)
	}
	if names["FormulaB__c"] {
		t.Error("FormulaB__c should be excluded by the closure")
	}
}

func TestValidationActiveComputedFields_FallsBackWithoutProvider(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)
	formula := "Id"

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     &formula,
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     &formula,
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	vs := NewValidationService(nil)
	// No projection provider set — should fall back to all active fields

	fields := vs.validationActiveComputedFields(context.Background(), schema)
	if len(fields) != 2 {
		t.Fatalf("expected all 2 active fields as fallback, got %d", len(fields))
	}
}
