package layoutfls

import (
	"context"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestFilterResolvesRelationshipNameDottedPaths(t *testing.T) {
	parentRel := "Parent__r"
	ownerRel := "Owner"
	schemas := map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "ContractId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Contract"}},
				{APIName: "Parent__c", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}, RelationshipName: &parentRel},
				{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser}, RelationshipName: &ownerRel},
			},
		},
		"Contract": {
			APIName: "Contract",
			Fields: []models.FieldMetadata{
				{APIName: "Applicable_Law__c", Type: constants.FieldTypeString},
			},
		},
		"Parent__c": {
			APIName: "Parent__c",
			Fields: []models.FieldMetadata{
				{APIName: "Secret__c", Type: constants.FieldTypeString},
				{APIName: "Public__c", Type: constants.FieldTypeString},
			},
		},
		constants.TableUser: {
			APIName: constants.TableUser,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
			},
		},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName:    {Readable: true, Createable: true},
				"ContractId":           {Readable: true, Createable: true},
				"Parent__c":            {Readable: true, Createable: true},
				constants.FieldOwnerID: {Readable: true, Createable: true},
			},
		},
		"Contract": {
			Fields: map[string]contract.FieldAccess{
				"Applicable_Law__c": {},
			},
		},
		"Parent__c": {
			Fields: map[string]contract.FieldAccess{
				"Secret__c": {},
				"Public__c": {Readable: true, Createable: true},
			},
		},
		constants.TableUser: {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {},
			},
		},
	}

	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
			return schemas[objectName], nil
		},
		func(_ context.Context, objectName string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[objectName], nil
		},
	)

	for _, expression := range []string{
		"record.Parent__r.Secret__c == 'x'",
		"record.Owner.Name != ''",
		"record.Contract.Applicable_Law__c == 'NL'",
	} {
		hidden, err := filter.TextReferencesHiddenField(expression)
		if err != nil {
			t.Fatalf("TextReferencesHiddenField(%q) error = %v", expression, err)
		}
		if !hidden {
			t.Fatalf("TextReferencesHiddenField(%q) = false, want true", expression)
		}
	}

	visible, err := filter.TextReferencesHiddenField("record.Parent__r.Public__c == 'ok'")
	if err != nil {
		t.Fatalf("TextReferencesHiddenField(visible) error = %v", err)
	}
	if visible {
		t.Fatal("TextReferencesHiddenField(visible) = true, want false")
	}

	accessByObject["Account"].Fields["Parent__c"] = contract.FieldAccess{}
	hiddenThroughUnreadableFK, err := filter.TextReferencesHiddenField("record.Parent__r.Public__c == 'ok'")
	if err != nil {
		t.Fatalf("TextReferencesHiddenField(hidden FK) error = %v", err)
	}
	if !hiddenThroughUnreadableFK {
		t.Fatal("TextReferencesHiddenField(hidden FK) = false, want true")
	}
}

func TestLookupExpansionReadableChecksHostAndTargetFLS(t *testing.T) {
	parentRel := "Parent__r"
	schemas := map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: "Parent__c", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}, RelationshipName: &parentRel},
			},
		},
		"Parent__c": {
			APIName: "Parent__c",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Secret__c", Type: constants.FieldTypeString},
			},
		},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {
			Fields: map[string]contract.FieldAccess{
				"Parent__c": {Readable: true},
			},
		},
		"Parent__c": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true},
				"Secret__c":         {},
			},
		},
	}
	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
			return schemas[objectName], nil
		},
		func(_ context.Context, objectName string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[objectName], nil
		},
	)

	for _, fieldPath := range []string{"Parent__c.Name", "Parent__r.Name"} {
		readable, err := filter.LookupExpansionReadable(fieldPath)
		if err != nil {
			t.Fatalf("LookupExpansionReadable(%q) error = %v", fieldPath, err)
		}
		if !readable {
			t.Fatalf("LookupExpansionReadable(%q) = false, want true", fieldPath)
		}
		hints, err := filter.FieldPathObjectHints(fieldPath)
		if err != nil {
			t.Fatalf("FieldPathObjectHints(%q) error = %v", fieldPath, err)
		}
		if len(hints) != 1 || hints[0] != "Parent__c" {
			t.Fatalf("FieldPathObjectHints(%q) = %#v, want [Parent__c]", fieldPath, hints)
		}
	}

	readable, err := filter.LookupExpansionReadable("Parent__c.Secret__c")
	if err != nil {
		t.Fatalf("LookupExpansionReadable(hidden target) error = %v", err)
	}
	if readable {
		t.Fatal("LookupExpansionReadable(hidden target) = true, want false")
	}

	accessByObject["Account"].Fields["Parent__c"] = contract.FieldAccess{}
	readable, err = filter.LookupExpansionReadable("Parent__c.Name")
	if err != nil {
		t.Fatalf("LookupExpansionReadable(hidden host FK) error = %v", err)
	}
	if readable {
		t.Fatal("LookupExpansionReadable(hidden host FK) = true, want false")
	}
}

func TestFilterMatchesHiddenFieldReferencesCaseInsensitively(t *testing.T) {
	schemas := map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Secret__c", Type: constants.FieldTypeString},
			},
		},
		"Quote": {
			APIName: "Quote",
			Fields: []models.FieldMetadata{
				{APIName: "SafeTarget__c", Type: constants.FieldTypeString},
			},
		},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true, Createable: true},
				"Secret__c":         {},
			},
		},
		"Quote": {
			Fields: map[string]contract.FieldAccess{
				"SafeTarget__c": {Readable: true, Createable: true},
			},
		},
	}

	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
			return schemas[objectName], nil
		},
		func(_ context.Context, objectName string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[objectName], nil
		},
	)

	for _, expression := range []string{"secret__c == true", "ISBLANK(SeCrEt__C)"} {
		hidden, err := filter.TextReferencesHiddenField(expression)
		if err != nil {
			t.Fatalf("TextReferencesHiddenField(%q) error = %v", expression, err)
		}
		if !hidden {
			t.Fatalf("TextReferencesHiddenField(%q) = false, want true", expression)
		}
	}
	if !ContainsFieldToken("ISBLANK(secret__c)", "Secret__c") {
		t.Fatal("ContainsFieldToken lower-case hidden field = false, want true")
	}
	if ContainsFieldToken("NotSecret__c == true", "Secret__c") {
		t.Fatal("ContainsFieldToken matched embedded field token")
	}
	if ContainsFieldToken("Secret__cExtra == true", "Secret__c") {
		t.Fatal("ContainsFieldToken matched prefix field token")
	}

	mappings, err := filter.FilterPrefillMappings("Quote", map[string]string{"safetarget__c": "Name"})
	if err != nil {
		t.Fatalf("FilterPrefillMappings(case-insensitive target) error = %v", err)
	}
	if got := mappings["safetarget__c"]; got != "Name" {
		t.Fatalf("FilterPrefillMappings kept %q, want Name", got)
	}

	visibility := "secret__c == true"
	actions, err := filter.FilterActions([]models.ActionConfig{{
		Name:                "hidden-action",
		Label:               "Hidden Action",
		Type:                "Custom",
		VisibilityCondition: &visibility,
	}})
	if err != nil {
		t.Fatalf("FilterActions(case-insensitive hidden visibility) error = %v", err)
	}
	if len(actions) != 0 {
		t.Fatalf("FilterActions kept hidden action: %+v", actions)
	}

	rules, err := filter.FilterRecordStateRules([]models.RecordStateRule{{
		ID:   "hidden-state",
		When: "SeCrEt__C == true",
	}})
	if err != nil {
		t.Fatalf("FilterRecordStateRules(case-insensitive hidden when) error = %v", err)
	}
	if len(rules) != 0 {
		t.Fatalf("FilterRecordStateRules kept hidden rule: %+v", rules)
	}
}

func TestFilterActionsPreservesAuthoredEmptySlice(t *testing.T) {
	filter := New(context.Background(), "Account", &contract.EffectiveObjectAccess{}, &models.ObjectMetadata{APIName: "Account"}, nil, nil)

	filtered, err := filter.FilterActions([]models.ActionConfig{})
	if err != nil {
		t.Fatalf("FilterActions(empty) error = %v", err)
	}
	if filtered == nil {
		t.Fatal("FilterActions(empty) returned nil, want non-nil empty slice")
	}
	if len(filtered) != 0 {
		t.Fatalf("FilterActions(empty) len = %d, want 0", len(filtered))
	}

	filtered, err = filter.FilterActions(nil)
	if err != nil {
		t.Fatalf("FilterActions(nil) error = %v", err)
	}
	if filtered != nil {
		t.Fatalf("FilterActions(nil) = %+v, want nil", filtered)
	}
}

func TestFilterActionsDropsLabelsThatReferenceHiddenFields(t *testing.T) {
	targetObject := "Quote"
	schemas := map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Secret__c", Type: constants.FieldTypeString},
				{APIName: "Status", Type: constants.FieldTypeString},
			},
		},
		targetObject: {
			APIName: targetObject,
			Fields: []models.FieldMetadata{
				{APIName: "HiddenTarget__c", Type: constants.FieldTypeString},
				{APIName: "SafeTarget__c", Type: constants.FieldTypeString},
				{APIName: "Status", Type: constants.FieldTypeString},
			},
		},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true},
				"Secret__c":         {},
				"Status":            {Readable: true},
			},
		},
		targetObject: {
			Fields: map[string]contract.FieldAccess{
				"HiddenTarget__c": {},
				"SafeTarget__c":   {Readable: true},
				"Status":          {},
			},
		},
	}
	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
			return schemas[objectName], nil
		},
		func(_ context.Context, objectName string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[objectName], nil
		},
	)

	actions, err := filter.FilterActions([]models.ActionConfig{
		{Name: "safe", Label: "Safe Action", Type: "Custom"},
		{Name: "base-owned-collision", Label: "Status", Type: "Custom", TargetObject: &targetObject},
		{Name: "hidden-base-label", Label: "Show Secret__c", Type: "Custom"},
		{Name: "hidden-target-label", Label: "Show HiddenTarget__c", Type: "Custom", TargetObject: &targetObject},
	})
	if err != nil {
		t.Fatalf("FilterActions(label hidden refs) error = %v", err)
	}
	if len(actions) != 2 || actions[0].Name != "safe" || actions[1].Name != "base-owned-collision" {
		t.Fatalf("FilterActions(label hidden refs) = %+v, want safe and base-owned collision actions", actions)
	}
}

func TestFilterScansRecordStateCopyAndHostLinkFields(t *testing.T) {
	parentRel := "Parent__r"
	ownerRel := "Owner"
	schemas := map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Parent__c", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}, RelationshipName: &parentRel},
				{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser, "Group"}, RelationshipName: &ownerRel},
				{APIName: "Quote__c", Type: constants.FieldTypeReference, ReferenceTo: []string{"Quote"}},
				{APIName: "HiddenQuote__c", Type: constants.FieldTypeReference, ReferenceTo: []string{"Quote"}},
			},
		},
		"Parent__c": {
			APIName: "Parent__c",
			Fields: []models.FieldMetadata{
				{APIName: "Secret__c", Type: constants.FieldTypeString},
				{APIName: "Public__c", Type: constants.FieldTypeString},
			},
		},
		"Quote": {
			APIName: "Quote",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser}, RelationshipName: &ownerRel},
				{APIName: "HiddenTarget__c", Type: constants.FieldTypeString},
				{APIName: "SafeTarget__c", Type: constants.FieldTypeString},
			},
		},
		constants.TableUser: {
			APIName: constants.TableUser,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
			},
		},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName:    {Readable: true, Createable: true},
				"Parent__c":            {Readable: true, Createable: true},
				constants.FieldOwnerID: {Readable: true, Createable: true},
				"Quote__c":             {Readable: true, Createable: true},
				"HiddenQuote__c":       {},
			},
		},
		"Parent__c": {
			Fields: map[string]contract.FieldAccess{
				"Secret__c": {},
				"Public__c": {Readable: true, Createable: true},
			},
		},
		"Quote": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldOwnerID: {},
				"HiddenTarget__c":      {Createable: true},
				"SafeTarget__c":        {Readable: true, Createable: true},
			},
		},
		constants.TableUser: {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true, Createable: true},
			},
		},
	}

	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
			return schemas[objectName], nil
		},
		func(_ context.Context, objectName string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[objectName], nil
		},
	)

	filtered, err := filter.FilterRecordStateRules([]models.RecordStateRule{
		{
			ID:    "safe",
			When:  constants.FieldName + " != ''",
			Links: []models.RecordStateLink{{ID: "safe-link", Label: "Owner", ObjectAPIName: constants.TableUser, ReferenceFieldAPIName: constants.FieldOwnerID}},
		},
		{
			ID:    "safe-implicit-target",
			When:  constants.FieldName + " != ''",
			Links: []models.RecordStateLink{{ID: "safe-implicit-link", Label: "Quote", ReferenceFieldAPIName: "Quote__c"}},
		},
		{
			ID:    "hidden-link-host-fk",
			When:  constants.FieldName + " != ''",
			Links: []models.RecordStateLink{{ID: "hidden-link", Label: "Quote", ObjectAPIName: "Quote", ReferenceFieldAPIName: "HiddenQuote__c"}},
		},
		{
			ID:    "hidden-link-label-copy",
			When:  constants.FieldName + " != ''",
			Links: []models.RecordStateLink{{ID: "hidden-label-link", Label: "HiddenTarget__c", ObjectAPIName: "Quote", ReferenceFieldAPIName: "Quote__c"}},
		},
		{
			ID:    "hidden-link-implicit-label-copy",
			When:  constants.FieldName + " != ''",
			Links: []models.RecordStateLink{{ID: "hidden-implicit-label-link", Label: "HiddenTarget__c", ReferenceFieldAPIName: "Quote__c"}},
		},
		{
			ID:    "hidden-link-ambiguous-implicit-target",
			When:  constants.FieldName + " != ''",
			Links: []models.RecordStateLink{{ID: "hidden-ambiguous-implicit-link", Label: "Owner", ReferenceFieldAPIName: constants.FieldOwnerID}},
		},
		{
			ID:       "mixed-hidden-link-safe-projection",
			When:     constants.FieldName + " != ''",
			Banner:   &models.RecordStateBanner{Title: "Safe banner", Body: "Safe copy"},
			ReadOnly: &models.RecordStateReadOnly{Enabled: true, Reason: "Safe reason"},
			Links:    []models.RecordStateLink{{ID: "mixed-hidden-link", Label: "Quote", ObjectAPIName: "Quote", ReferenceFieldAPIName: "HiddenQuote__c"}},
		},
		{
			ID:     "hidden-banner-copy",
			When:   constants.FieldName + " != ''",
			Banner: &models.RecordStateBanner{Body: "record.Parent__r.Secret__c blocks save"},
		},
		{
			ID:              "hidden-override-copy",
			When:            constants.FieldName + " != ''",
			ActionOverrides: []models.RecordStateActionOverride{{ActionKey: "host:edit-record", Mode: "disable", Reason: "Parent__r.Secret__c is locked"}},
		},
		{
			ID:       "hidden-readonly-copy",
			When:     constants.FieldName + " != ''",
			ReadOnly: &models.RecordStateReadOnly{Enabled: true, Reason: "Quote.HiddenTarget__c is locked"},
		},
	})
	if err != nil {
		t.Fatalf("FilterRecordStateRules() error = %v", err)
	}
	byID := map[string]models.RecordStateRule{}
	for _, rule := range filtered {
		byID[rule.ID] = rule
	}
	if len(byID) != 7 {
		t.Fatalf("FilterRecordStateRules() kept IDs = %+v, want 7 rules", byID)
	}
	for _, id := range []string{"safe", "safe-implicit-target"} {
		rule, ok := byID[id]
		if !ok {
			t.Fatalf("FilterRecordStateRules() missing safe rule %q: %+v", id, filtered)
		}
		if len(rule.Links) != 1 {
			t.Fatalf("rule %q links = %+v, want one safe link", id, rule.Links)
		}
	}
	for _, id := range []string{"hidden-link-host-fk", "hidden-link-label-copy", "hidden-link-implicit-label-copy", "hidden-link-ambiguous-implicit-target"} {
		rule, ok := byID[id]
		if !ok {
			t.Fatalf("FilterRecordStateRules() removed link-only rule %q; filtered=%+v", id, filtered)
		}
		if len(rule.Links) != 0 {
			t.Fatalf("rule %q links = %+v, want hidden links removed", id, rule.Links)
		}
	}
	mixed, ok := byID["mixed-hidden-link-safe-projection"]
	if !ok {
		t.Fatalf("FilterRecordStateRules() removed mixed safe projection rule; filtered=%+v", filtered)
	}
	if len(mixed.Links) != 0 || mixed.Banner == nil || mixed.ReadOnly == nil || !mixed.ReadOnly.Enabled {
		t.Fatalf("mixed rule = %+v, want banner/readOnly preserved with hidden link removed", mixed)
	}
	for _, id := range []string{"hidden-banner-copy", "hidden-override-copy", "hidden-readonly-copy"} {
		if _, ok := byID[id]; ok {
			t.Fatalf("FilterRecordStateRules() kept text-leaking rule %q: %+v", id, filtered)
		}
	}
}

func TestFilterActionPreservesBaseOwnedFieldsWhenTargetHidesSameName(t *testing.T) {
	schemas := map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Status", Type: constants.FieldTypeString},
			},
		},
		"Quote": {
			APIName: "Quote",
			Fields: []models.FieldMetadata{
				{APIName: "Status", Type: constants.FieldTypeString},
				{APIName: "SafeTarget__c", Type: constants.FieldTypeString},
				{APIName: "targetObject", Type: constants.FieldTypeString},
			},
		},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true, Createable: true},
				"Status":            {Readable: true, Createable: true},
			},
		},
		"Quote": {
			Fields: map[string]contract.FieldAccess{
				"Status":        {},
				"SafeTarget__c": {Readable: true, Createable: true},
				"targetObject":  {},
			},
		},
	}

	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
			return schemas[objectName], nil
		},
		func(_ context.Context, objectName string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[objectName], nil
		},
	)

	visibility := "record.Status == 'Open'"
	actions, err := filter.FilterActions([]models.ActionConfig{{
		Name:                "safe-collision",
		Label:               "Safe Collision",
		Type:                "NavigateToNewRecord",
		TargetObject:        stringPtr("Quote"),
		VisibilityCondition: &visibility,
		Config: map[string]interface{}{
			"lookupField":     "SafeTarget__c",
			"prefillMappings": map[string]interface{}{"SafeTarget__c": "Status"},
		},
	}})
	if err != nil {
		t.Fatalf("FilterActions() error = %v", err)
	}
	if len(actions) != 1 {
		t.Fatalf("FilterActions() len = %d, want 1", len(actions))
	}
	if actions[0].VisibilityCondition == nil || *actions[0].VisibilityCondition != visibility {
		t.Fatalf("visibility condition = %v, want %q", actions[0].VisibilityCondition, visibility)
	}
	if got := actions[0].Config["lookupField"]; got != "SafeTarget__c" {
		t.Fatalf("lookupField = %v, want SafeTarget__c", got)
	}
	mappings := actions[0].Config["prefillMappings"].(map[string]string)
	if got := mappings["SafeTarget__c"]; got != "Status" {
		t.Fatalf("prefill source = %q, want Status", got)
	}

	actions, err = filter.FilterActions([]models.ActionConfig{{
		Name:  "safe-config-target-object",
		Label: "Safe Config Target Object",
		Type:  "NavigateToNewRecord",
		Config: map[string]interface{}{
			"targetObject":    "Quote",
			"lookupField":     "SafeTarget__c",
			"prefillMappings": map[string]interface{}{"SafeTarget__c": "Status"},
		},
	}})
	if err != nil {
		t.Fatalf("FilterActions(config target object) error = %v", err)
	}
	if len(actions) != 1 {
		t.Fatalf("FilterActions(config target object) len = %d, want 1", len(actions))
	}
	if got := actions[0].Config["targetObject"]; got != "Quote" {
		t.Fatalf("targetObject config = %v, want Quote", got)
	}
}

func TestFilterFailsClosedForMultiHopAndPolymorphicReferences(t *testing.T) {
	parentRel := "Parent__r"
	ownerRel := "Owner"
	whatRel := "What"
	schemas := map[string]*models.ObjectMetadata{
		"Account": {
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Parent__c", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}, RelationshipName: &parentRel},
				{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser, "Group"}, RelationshipName: &ownerRel},
				{APIName: "WhatId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Contract", "Opportunity"}, RelationshipName: &whatRel},
			},
		},
		"Parent__c": {
			APIName: "Parent__c",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser}, RelationshipName: &ownerRel},
			},
		},
		constants.TableUser: {
			APIName: constants.TableUser,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Secret__c", Type: constants.FieldTypeString},
			},
		},
		"Group": {
			APIName: "Group",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
			},
		},
		"Contract": {
			APIName: "Contract",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Secret__c", Type: constants.FieldTypeString},
				{APIName: "Public__c", Type: constants.FieldTypeString},
			},
		},
		"Opportunity": {
			APIName: "Opportunity",
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldName, Type: constants.FieldTypeString},
				{APIName: "Secret__c", Type: constants.FieldTypeString},
				{APIName: "Public__c", Type: constants.FieldTypeString},
			},
		},
		"Quote": {
			APIName: "Quote",
			Fields: []models.FieldMetadata{
				{APIName: "SafeTarget__c", Type: constants.FieldTypeString},
				{APIName: "PolyTarget__c", Type: constants.FieldTypeString},
				{APIName: "HiddenFKTarget__c", Type: constants.FieldTypeString},
			},
		},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName:    {Readable: true, Createable: true},
				"Parent__c":            {Readable: true, Createable: true},
				constants.FieldOwnerID: {Readable: true, Createable: true},
				"WhatId":               {Readable: true, Createable: true},
			},
		},
		"Parent__c": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldOwnerID: {Readable: true, Createable: true},
			},
		},
		constants.TableUser: {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true, Createable: true},
				"Secret__c":         {},
			},
		},
		"Group": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {},
			},
		},
		"Contract": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true, Createable: true},
				"Secret__c":         {},
				"Public__c":         {Readable: true, Createable: true},
			},
		},
		"Opportunity": {
			Fields: map[string]contract.FieldAccess{
				constants.FieldName: {Readable: true, Createable: true},
				"Secret__c":         {Readable: true, Createable: true},
				"Public__c":         {Readable: true, Createable: true},
			},
		},
		"Quote": {
			Fields: map[string]contract.FieldAccess{
				"SafeTarget__c":     {Readable: true, Createable: true},
				"PolyTarget__c":     {Readable: true, Createable: true},
				"HiddenFKTarget__c": {Readable: true, Createable: true},
			},
		},
	}

	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, objectName string) (*models.ObjectMetadata, error) {
			return schemas[objectName], nil
		},
		func(_ context.Context, objectName string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[objectName], nil
		},
	)

	for _, expression := range []string{
		"record.Parent__r.Owner.Secret__c == true",
		"record.Owner.Name != ''",
		"record.What.Secret__c != ''",
		"record.Parent__r.MissingRelationship.Secret__c == true",
	} {
		hidden, err := filter.TextReferencesHiddenField(expression)
		if err != nil {
			t.Fatalf("TextReferencesHiddenField(%q) error = %v", expression, err)
		}
		if !hidden {
			t.Fatalf("TextReferencesHiddenField(%q) = false, want true", expression)
		}
	}

	visible, err := filter.TextReferencesHiddenField("record.What.Public__c != ''")
	if err != nil {
		t.Fatalf("TextReferencesHiddenField(visible polymorphic) error = %v", err)
	}
	if visible {
		t.Fatal("TextReferencesHiddenField(visible polymorphic) = true, want false")
	}

	actions, err := filter.FilterActions([]models.ActionConfig{{
		Name:         "prefill",
		Label:        "Prefill",
		Type:         "NavigateToNewRecord",
		TargetObject: stringPtr("Quote"),
		Config: map[string]interface{}{
			"prefillMappings": map[string]interface{}{
				"SafeTarget__c": "Name",
				"PolyTarget__c": "What.Secret__c",
			},
		},
	}})
	if err != nil {
		t.Fatalf("FilterActions(polymorphic prefill) error = %v", err)
	}
	mappings := actions[0].Config["prefillMappings"].(map[string]string)
	if got := mappings["SafeTarget__c"]; got != "Name" {
		t.Fatalf("safe prefill mapping = %q, want Name", got)
	}
	if _, ok := mappings["PolyTarget__c"]; ok {
		t.Fatalf("hidden polymorphic prefill mapping survived: %+v", mappings)
	}

	accessByObject["Account"].Fields["WhatId"] = contract.FieldAccess{}
	actions, err = filter.FilterActions([]models.ActionConfig{{
		Name:         "hidden-fk-prefill",
		Label:        "Hidden FK Prefill",
		Type:         "NavigateToNewRecord",
		TargetObject: stringPtr("Quote"),
		Config: map[string]interface{}{
			"prefillMappings": map[string]interface{}{
				"HiddenFKTarget__c": "What.Public__c",
			},
		},
	}})
	if err != nil {
		t.Fatalf("FilterActions(hidden FK prefill) error = %v", err)
	}
	if len(actions) != 1 {
		t.Fatalf("FilterActions(hidden FK prefill) len = %d, want 1", len(actions))
	}
	if _, ok := actions[0].Config["prefillMappings"]; ok {
		t.Fatalf("hidden-FK prefill mappings survived: %+v", actions[0].Config)
	}
}

func stringPtr(value string) *string {
	return &value
}

// A readable related field must not be dropped just because its name collides
// with a hidden field of the base object (cross-object name collision). The
// catch-all token scan previously re-flagged the resolved-readable terminal.
func TestFilterDoesNotOverRemoveOnCrossObjectNameCollision(t *testing.T) {
	ownerRel := "Owner"
	schemas := map[string]*models.ObjectMetadata{
		"Account": {APIName: "Account", Fields: []models.FieldMetadata{
			{APIName: constants.FieldName, Type: constants.FieldTypeString},
			{APIName: "Rating", Type: constants.FieldTypeString},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, ReferenceTo: []string{constants.TableUser}, RelationshipName: &ownerRel},
		}},
		constants.TableUser: {APIName: constants.TableUser, Fields: []models.FieldMetadata{
			{APIName: "Rating", Type: constants.FieldTypeString},
		}},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {Fields: map[string]contract.FieldAccess{
			constants.FieldName:    {Readable: true},
			"Rating":               {}, // hidden on base
			constants.FieldOwnerID: {Readable: true},
		}},
		constants.TableUser: {Fields: map[string]contract.FieldAccess{
			"Rating": {Readable: true}, // readable on User
		}},
	}
	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, n string) (*models.ObjectMetadata, error) { return schemas[n], nil },
		func(_ context.Context, n string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[n], nil
		},
	)
	hidden, err := filter.TextReferencesHiddenField("record.Owner.Rating != null")
	if err != nil {
		t.Fatal(err)
	}
	if hidden {
		t.Fatal("record.Owner.Rating (readable User.Rating) wrongly flagged — cross-object collision over-removal")
	}
	// A bare reference to the hidden base Rating must still be flagged.
	hidden, err = filter.TextReferencesHiddenField("Rating > 3")
	if err != nil {
		t.Fatal(err)
	}
	if !hidden {
		t.Fatal("bare reference to a hidden base field must be flagged")
	}
}

// A transitional reference field (relationship name set, no declared targets)
// must fail closed on any path through it, including the bare form (only the
// record-prefixed form failed closed before).
func TestFilterFailsClosedOnTransitionalRelationship(t *testing.T) {
	ownerRel := "Owner"
	schemas := map[string]*models.ObjectMetadata{
		"Account": {APIName: "Account", Fields: []models.FieldMetadata{
			{APIName: constants.FieldName, Type: constants.FieldTypeString},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, RelationshipName: &ownerRel}, // empty ReferenceTo
		}},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {Fields: map[string]contract.FieldAccess{
			constants.FieldName:    {Readable: true},
			constants.FieldOwnerID: {Readable: true},
		}},
	}
	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, n string) (*models.ObjectMetadata, error) { return schemas[n], nil },
		func(_ context.Context, n string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[n], nil
		},
	)
	for _, expr := range []string{"Owner.Secret__c != null", "record.Owner.Secret__c != null"} {
		hidden, err := filter.TextReferencesHiddenField(expr)
		if err != nil {
			t.Fatalf("TextReferencesHiddenField(%q) error = %v", expr, err)
		}
		if !hidden {
			t.Fatalf("transitional relationship path %q must fail closed", expr)
		}
	}
}

// A non-reference field whose API name infers to the segment must NOT be
// treated as a relationship — otherwise recognizing it (and failing closed on
// its empty target set) would over-remove a bare path that names no hidden field.
func TestFilterDoesNotTreatNonReferenceFieldAsRelationship(t *testing.T) {
	schemas := map[string]*models.ObjectMetadata{
		"Account": {APIName: "Account", Fields: []models.FieldMetadata{
			{APIName: constants.FieldName, Type: constants.FieldTypeString},
			{APIName: "Discount__c", Type: constants.FieldTypeString}, // not a reference; infers "Discount__r"
		}},
	}
	accessByObject := map[string]*contract.EffectiveObjectAccess{
		"Account": {Fields: map[string]contract.FieldAccess{
			constants.FieldName: {Readable: true},
			"Discount__c":       {Readable: true},
		}},
	}
	filter := New(context.Background(), "Account", accessByObject["Account"], schemas["Account"],
		func(_ context.Context, n string) (*models.ObjectMetadata, error) { return schemas[n], nil },
		func(_ context.Context, n string) (*contract.EffectiveObjectAccess, error) {
			return accessByObject[n], nil
		},
	)
	hidden, err := filter.TextReferencesHiddenField("Discount__r.Amount > 0")
	if err != nil {
		t.Fatal(err)
	}
	if hidden {
		t.Fatal("non-reference field must not be treated as a relationship (over-removal)")
	}
}
