package analytics

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

	"github.com/nexuscrm/kernel/pkg/ports"
	domainrls "github.com/nexuscrm/kernel/pkg/rls"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

func TestQueryRejectsFLSDeniedReadClauseFields(t *testing.T) {
	for _, tc := range []struct {
		name   string
		mutate func(*queryspec.QuerySpec)
	}{
		{
			name: "filter expr",
			mutate: func(req *queryspec.QuerySpec) {
				req.Filter = queryspec.FormulaPredicate("Comments != nil")
			},
		},
		{
			name: "criteria",
			mutate: func(req *queryspec.QuerySpec) {
				req.Filter = queryspec.CriteriaPredicate([]queryspec.QueryCriterion{{Field: "Comments", Op: "!=", Val: nil}})
			},
		},
		{
			name: "sort field",
			mutate: func(req *queryspec.QuerySpec) {
				req.SortField = "Comments"
			},
		},
		{
			name: "order by",
			mutate: func(req *queryspec.QuerySpec) {
				req.OrderBy = []queryspec.SortCriterion{{Field: "Comments", Direction: "ASC"}}
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := clauseFLSQueryService(repo)
			req := queryspec.QuerySpec{
				ObjectAPIName: constants.TableProcessInstanceHistory,
				SelectFields:  []string{constants.FieldID},
			}
			tc.mutate(&req)

			_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if err == nil || !strings.Contains(err.Error(), "Comments") {
				t.Fatalf("Query error = %v, want denied Comments field", err)
			}
			if repo.findCalled {
				t.Fatal("repo.Find was called after a denied read clause")
			}
		})
	}
}

func TestQueryParentFilterPathValidationContract(t *testing.T) {
	explicitRel := "Customer"
	whoRel := "Who"
	activeFormulaMode := "active"
	writeMaterializedMode := "write_materialized"
	formulaReturnType := constants.FieldTypeDouble
	for _, tc := range []struct {
		name       string
		filterExpr string
		rootFields []models.FieldMetadata
		schemas    []*models.ObjectMetadata
		rels       []*models.Relationship
		want       string
		wantPlan   *parentFilterPlanAssertion
	}{
		{
			name:       "explicit relationship name",
			filterExpr: "Customer.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}, RelationshipName: &explicitRel},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Customer.Industry", "Customer", "AccountId", "Account", "Industry"),
		},
		{
			name:       "derived traversal name",
			filterExpr: "Account.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "direct foreign key API name",
			filterExpr: "AccountId.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			want: "relationship field \"AccountId\" is not declared",
		},
		{
			name:       "scalar field shadowing relationship traversal",
			filterExpr: "Account.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "Account", Type: constants.FieldTypeString},
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "legacy persisted lookup alias",
			filterExpr: "Account.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.SchemaFieldType("lookup"), ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "logical Name maps to target name field",
			filterExpr: "Issue.Name = 'ISS-001'",
			rootFields: []models.FieldMetadata{
				{APIName: "IssueId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Issue"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterObjectSchema("Issue",
				models.FieldMetadata{APIName: "Subject", Type: constants.FieldTypeString, IsNameField: true},
			)},
			wantPlan: defaultParentFilterPlan("Issue.Name", "Issue", "IssueId", "Issue", "Subject"),
		},
		{
			name:       "nil comparison",
			filterExpr: "Account.Industry == nil",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "not nil comparison",
			filterExpr: "Account.Industry != nil",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "parent in literal list",
			filterExpr: "Account.Industry in ['Technology', 'Finance']",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "parent not in literal list",
			filterExpr: "Account.Industry not in ['Technology', 'Finance']",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "not function parent in literal list",
			filterExpr: "NOT(Account.Industry in ['Technology', 'Finance'])",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "parent contains string literal",
			filterExpr: "Account.Industry contains 'Tech'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "parent startsWith string literal",
			filterExpr: "Account.Industry startsWith 'Tech'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "parent endsWith string literal",
			filterExpr: "Account.Industry endsWith 'Tech'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "negated parent contains string literal",
			filterExpr: "!(Account.Industry contains 'Tech')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "negated parent startsWith string literal",
			filterExpr: "!(Account.Industry startsWith 'Tech')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "negated parent endsWith string literal",
			filterExpr: "!(Account.Industry endsWith 'Tech')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "not function parent contains string literal",
			filterExpr: "NOT(Account.Industry contains 'Tech')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "not function parent startsWith string literal",
			filterExpr: "NOT(Account.Industry startsWith 'Tech')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "not function parent endsWith string literal",
			filterExpr: "NOT(Account.Industry endsWith 'Tech')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "parent isblank helper",
			filterExpr: "ISBLANK(Account.Industry)",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "negated parent isblank helper",
			filterExpr: "NOT(ISBLANK(Account.Industry))",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			wantPlan: defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
		},
		{
			name:       "relationship overlay authorizes renamed traversal",
			filterExpr: "Customer.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Opportunity",
				ParentObjectAPIName: "Account",
				FieldAPIName:        "AccountId",
				RelationshipName:    "Customer",
			}},
			wantPlan: defaultParentFilterPlan("Customer.Industry", "Customer", "AccountId", "Account", "Industry"),
		},
		{
			name:       "relationship overlay rejects stale derived traversal",
			filterExpr: "Account.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Opportunity",
				ParentObjectAPIName: "Account",
				FieldAPIName:        "AccountId",
				RelationshipName:    "Customer",
			}},
			want: "relationship field \"Account\" is not declared",
		},
		{
			name:       "polymorphic relationship",
			filterExpr: "Who.Name = 'Ada Lovelace'",
			rootFields: []models.FieldMetadata{
				{APIName: "WhoId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Contact", "Lead"}, RelationshipName: &whoRel, IsPolymorphic: true},
			},
			want: "polymorphic relationship filters are not supported",
		},
		{
			name:       "single-target polymorphic relationship",
			filterExpr: "Who.Name = 'Ada Lovelace'",
			rootFields: []models.FieldMetadata{
				{APIName: "WhoId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Contact"}, RelationshipName: &whoRel, IsPolymorphic: true},
			},
			schemas: []*models.ObjectMetadata{parentFilterObjectSchema("Contact",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			wantPlan: &parentFilterPlanAssertion{
				rawPath:        "Who.Name",
				relationship:   "Who",
				rootFK:         "WhoId",
				rootFKPhysical: "WhoId",
				targetObject:   "Contact",
				targetTable:    "Contact",
				targetID:       constants.FieldID,
				targetField:    "Name",
				targetPhysical: "Name",
			},
		},
		{
			name:       "missing target schema",
			filterExpr: "Account.Industry = 'Technology'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			want: "target object \"Account\" is unavailable",
		},
		{
			name:       "compound virtual target leaf",
			filterExpr: "Account.Name = 'Ada Lovelace'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString, IsVirtual: true, VirtualKind: constants.VirtualFieldCompound, IsCompound: true, ComposedFrom: []string{"FirstName", "LastName"}},
				models.FieldMetadata{APIName: "FirstName", Type: constants.FieldTypeString, PhysicalName: "first_name"},
				models.FieldMetadata{APIName: "LastName", Type: constants.FieldTypeString, PhysicalName: "last_name"},
			)},
			wantPlan: &parentFilterPlanAssertion{
				rawPath:             "Account.Name",
				relationship:        "Account",
				rootFK:              "AccountId",
				rootFKPhysical:      "AccountId",
				targetObject:        "Account",
				targetTable:         "Account",
				targetID:            constants.FieldID,
				targetField:         constants.FieldName,
				targetCompoundParts: []string{"first_name", "last_name"},
			},
		},
		{
			name:       "stored formula target leaf",
			filterExpr: "Account.Score = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:            "Score",
					Type:               constants.FieldTypeFormula,
					Formula:            strPtr("AnnualRevenue * 2"),
					ReturnType:         &formulaReturnType,
					FormulaMode:        &activeFormulaMode,
					FormulaStorageMode: &writeMaterializedMode,
					PhysicalName:       "score_stored",
				},
			)},
			wantPlan: &parentFilterPlanAssertion{
				rawPath:        "Account.Score",
				relationship:   "Account",
				rootFK:         "AccountId",
				rootFKPhysical: "AccountId",
				targetObject:   "Account",
				targetTable:    "Account",
				targetID:       constants.FieldID,
				targetField:    "Score",
				targetPhysical: "score_stored",
			},
		},
		{
			name:       "view-only formula target leaf",
			filterExpr: "Account.Score = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "Score",
					Type:         constants.FieldTypeFormula,
					Formula:      strPtr("AnnualRevenue * 2"),
					ReturnType:   &formulaReturnType,
					FormulaMode:  &activeFormulaMode,
					PhysicalName: "score_view",
				},
			)},
			want: "must target a physical or compound field",
		},
		{
			name:       "formula target leaf without expression",
			filterExpr: "Account.Score = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "Score",
					Type:         constants.FieldTypeFormula,
					ReturnType:   &formulaReturnType,
					PhysicalName: "score_stored",
				},
			)},
			want: "must target a physical or compound field",
		},
		{
			name:       "stored rollup target leaf",
			filterExpr: "Account.TotalAmount = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "TotalAmount",
					Type:         constants.FieldTypeRollupSummary,
					PhysicalName: "total_amount",
					RollupConfig: &models.RollupConfig{
						SummaryObject:     "OpportunityLineItem",
						SummaryField:      "Amount",
						RelationshipField: "AccountId",
						CalcType:          "SUM",
					},
				},
			), parentFilterObjectSchema("OpportunityLineItem",
				models.FieldMetadata{APIName: "Amount", Type: constants.FieldTypeCurrency},
				models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			)},
			wantPlan: &parentFilterPlanAssertion{
				rawPath:        "Account.TotalAmount",
				relationship:   "Account",
				rootFK:         "AccountId",
				rootFKPhysical: "AccountId",
				targetObject:   "Account",
				targetTable:    "Account",
				targetID:       constants.FieldID,
				targetField:    "TotalAmount",
				targetPhysical: "total_amount",
			},
		},
		{
			name:       "rollup target leaf without rollup config",
			filterExpr: "Account.TotalAmount = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "TotalAmount",
					Type:         constants.FieldTypeRollupSummary,
					PhysicalName: "total_amount",
				},
			)},
			want: "must target a physical or compound field",
		},
		{
			name:       "rollup target leaf with empty rollup config",
			filterExpr: "Account.TotalAmount = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "TotalAmount",
					Type:         constants.FieldTypeRollupSummary,
					PhysicalName: "total_amount",
					RollupConfig: &models.RollupConfig{},
				},
			)},
			want: "must target a physical or compound field",
		},
		{
			name:       "non-count rollup target leaf without summary field",
			filterExpr: "Account.TotalAmount = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "TotalAmount",
					Type:         constants.FieldTypeRollupSummary,
					PhysicalName: "total_amount",
					RollupConfig: &models.RollupConfig{
						SummaryObject:     "OpportunityLineItem",
						RelationshipField: "AccountId",
						CalcType:          "SUM",
					},
				},
			), parentFilterObjectSchema("OpportunityLineItem",
				models.FieldMetadata{APIName: "Amount", Type: constants.FieldTypeCurrency},
				models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			)},
			want: "must target a physical or compound field",
		},
		{
			name:       "rollup target leaf with unsupported calc type",
			filterExpr: "Account.TotalAmount = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "TotalAmount",
					Type:         constants.FieldTypeRollupSummary,
					PhysicalName: "total_amount",
					RollupConfig: &models.RollupConfig{
						SummaryObject:     "OpportunityLineItem",
						SummaryField:      "Amount",
						RelationshipField: "AccountId",
						CalcType:          "MEDIAN",
					},
				},
			), parentFilterObjectSchema("OpportunityLineItem",
				models.FieldMetadata{APIName: "Amount", Type: constants.FieldTypeCurrency},
				models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			)},
			want: "must target a physical or compound field",
		},
		{
			name:       "sum rollup target leaf with nonnumeric summary field",
			filterExpr: "Account.TotalAmount = 100",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{
					APIName:      "TotalAmount",
					Type:         constants.FieldTypeRollupSummary,
					PhysicalName: "total_amount",
					RollupConfig: &models.RollupConfig{
						SummaryObject:     "OpportunityLineItem",
						SummaryField:      "Description",
						RelationshipField: "AccountId",
						CalcType:          "SUM",
					},
				},
			), parentFilterObjectSchema("OpportunityLineItem",
				models.FieldMetadata{APIName: "Description", Type: constants.FieldTypeString},
				models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			)},
			want: "must target a physical or compound field",
		},
		{
			name:       "multi-hop relationship",
			filterExpr: "Account.Owner.Name = 'Ada Lovelace'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: &parentFilterPlanAssertion{
				rawPath:        "Account.Owner.Name",
				relationship:   "Account",
				rootFK:         "AccountId",
				rootFKPhysical: "AccountId",
				targetObject:   "User",
				targetTable:    "User",
				targetID:       constants.FieldID,
				targetField:    "Name",
				targetPhysical: "Name",
				hops: []parentFilterHopAssertion{
					{
						relationship:    "Account",
						sourceObject:    "Opportunity",
						foreignKey:      "AccountId",
						foreignPhysical: "AccountId",
						targetObject:    "Account",
						targetTable:     "Account",
						targetID:        constants.FieldID,
					},
					{
						relationship:    "Owner",
						sourceObject:    "Account",
						foreignKey:      "OwnerId",
						foreignPhysical: "OwnerId",
						targetObject:    "User",
						targetTable:     "User",
						targetID:        constants.FieldID,
					},
				},
			},
		},
		{
			name:       "single-target polymorphic multi-hop relationship",
			filterExpr: "Account.Actor.Name = 'Ada Lovelace'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "ActorId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}, RelationshipName: strPtr("Actor"), IsPolymorphic: true},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "ActorId",
				RelationshipName:    "Actor",
			}},
			wantPlan: &parentFilterPlanAssertion{
				rawPath:        "Account.Actor.Name",
				relationship:   "Account",
				rootFK:         "AccountId",
				rootFKPhysical: "AccountId",
				targetObject:   "User",
				targetTable:    "User",
				targetID:       constants.FieldID,
				targetField:    "Name",
				targetPhysical: "Name",
				hops: []parentFilterHopAssertion{
					{
						relationship:    "Account",
						sourceObject:    "Opportunity",
						foreignKey:      "AccountId",
						foreignPhysical: "AccountId",
						targetObject:    "Account",
						targetTable:     "Account",
						targetID:        constants.FieldID,
					},
					{
						relationship:    "Actor",
						sourceObject:    "Account",
						foreignKey:      "ActorId",
						foreignPhysical: "ActorId",
						targetObject:    "User",
						targetTable:     "User",
						targetID:        constants.FieldID,
					},
				},
			},
		},
		{
			name:       "multi-hop parent in literal list",
			filterExpr: "Account.Owner.Name in ['Ada Lovelace', 'Grace Hopper']",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "multi-hop parent not in literal list",
			filterExpr: "Account.Owner.Name not in ['Ada Lovelace', 'Grace Hopper']",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "negated multi-hop parent in literal list",
			filterExpr: "!(Account.Owner.Name in ['Ada Lovelace', 'Grace Hopper'])",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "not function multi-hop parent in literal list",
			filterExpr: "NOT(Account.Owner.Name in ['Ada Lovelace', 'Grace Hopper'])",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "multi-hop parent contains string literal",
			filterExpr: "Account.Owner.Name contains 'Ada'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "multi-hop parent startsWith string literal",
			filterExpr: "Account.Owner.Name startsWith 'Ada'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "multi-hop parent endsWith string literal",
			filterExpr: "Account.Owner.Name endsWith 'Lovelace'",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "negated multi-hop parent contains string literal",
			filterExpr: "!(Account.Owner.Name contains 'Ada')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "negated multi-hop parent startsWith string literal",
			filterExpr: "!(Account.Owner.Name startsWith 'Ada')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "negated multi-hop parent endsWith string literal",
			filterExpr: "!(Account.Owner.Name endsWith 'Lovelace')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "call-form negated multi-hop parent contains string literal",
			filterExpr: "NOT(Account.Owner.Name contains 'Ada')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "call-form negated multi-hop parent startsWith string literal",
			filterExpr: "NOT(Account.Owner.Name startsWith 'Ada')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "call-form negated multi-hop parent endsWith string literal",
			filterExpr: "NOT(Account.Owner.Name endsWith 'Lovelace')",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "multi-hop parent is blank helper",
			filterExpr: "ISBLANK(Account.Owner.Name)",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "multi-hop parent is null helper",
			filterExpr: "ISNULL(Account.Owner.Name)",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "call-form negated multi-hop parent is blank helper",
			filterExpr: "NOT(ISBLANK(Account.Owner.Name))",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "bang-negated multi-hop parent is blank helper",
			filterExpr: "!ISBLANK(Account.Owner.Name)",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "call-form negated multi-hop parent is null helper",
			filterExpr: "NOT(ISNULL(Account.Owner.Name))",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
		{
			name:       "bang-negated multi-hop parent is null helper",
			filterExpr: "!ISNULL(Account.Owner.Name)",
			rootFields: []models.FieldMetadata{
				{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
			},
			schemas: []*models.ObjectMetadata{parentFilterAccountSchema(
				models.FieldMetadata{APIName: "OwnerId", Type: constants.FieldTypeReference, ReferenceTo: []string{"User"}},
			), parentFilterObjectSchema("User",
				models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString},
			)},
			rels: []*models.Relationship{{
				ChildObjectAPIName:  "Account",
				ParentObjectAPIName: "User",
				FieldAPIName:        "OwnerId",
				RelationshipName:    "Owner",
			}},
			wantPlan: multiHopOwnerNamePlan(),
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := parentFilterQueryServiceWithRelationships(repo, parentFilterRootSchema(tc.rootFields...), tc.rels, tc.schemas...)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
				Filter:        queryspec.FormulaPredicate(tc.filterExpr),
			}

			_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if tc.want != "" {
				if err == nil || !strings.Contains(err.Error(), tc.want) {
					t.Fatalf("Query error = %v, want %q", err, tc.want)
				}
				if repo.findCalled {
					t.Fatal("repo.Find was called after a parent-field filter validation failure")
				}
				return
			}
			if err != nil {
				t.Fatalf("QueryWithSpec error = %v, want accepted parent-field filter", err)
			}
			if !repo.findCalled {
				t.Fatal("repo.Find was not called for an accepted parent-field filter")
			}
			if tc.wantPlan == nil {
				t.Fatal("wantPlan is nil for accepted parent-field filter")
			}
			assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, *tc.wantPlan)
		})
	}
}

func TestQueryParentFilterBooleanCompositionBuildsExecutionPlans(t *testing.T) {
	for _, tc := range []struct {
		name       string
		filterExpr string
		wantPlans  []parentFilterPlanAssertion
	}{
		{
			name:       "root and parent predicate",
			filterExpr: "Account.Industry = 'Technology' AND StageName = 'Prospecting'",
			wantPlans: []parentFilterPlanAssertion{
				*defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
			},
		},
		{
			name:       "root side or predicate",
			filterExpr: "StageName = 'Prospecting' OR Account.Industry = 'Technology'",
			wantPlans: []parentFilterPlanAssertion{
				*defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
			},
		},
		{
			name:       "repeated parent path",
			filterExpr: "Account.Industry = 'Technology' OR Account.Industry = 'Software'",
			wantPlans: []parentFilterPlanAssertion{
				*defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
			},
		},
		{
			name:       "two target fields on same relationship",
			filterExpr: "Account.Industry = 'Technology' AND Account.Rating = 'Hot'",
			wantPlans: []parentFilterPlanAssertion{
				*defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"),
				*defaultParentFilterPlan("Account.Rating", "Account", "AccountId", "Account", "Rating"),
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := parentFilterQueryService(repo,
				parentFilterRootSchema(
					models.FieldMetadata{APIName: "StageName", Type: constants.FieldTypeString},
					models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
				),
				parentFilterAccountSchema(
					models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
					models.FieldMetadata{APIName: "Rating", Type: constants.FieldTypeString},
				),
			)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
				Filter:        queryspec.FormulaPredicate(tc.filterExpr),
			}

			if _, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"}); err != nil {
				t.Fatalf("QueryWithSpec error = %v, want accepted parent-field boolean filter", err)
			}
			if !repo.findCalled {
				t.Fatal("repo.Find was not called for an accepted parent-field boolean filter")
			}
			assertParentFilterPlans(t, repo.findReq.ParentFilterPaths, tc.wantPlans...)
		})
	}
}

func TestQueryParentFilterBlankCoalescingPredicateBuildsExecutionPlan(t *testing.T) {
	for _, tc := range []struct {
		name       string
		filterExpr string
	}{
		{
			name:       "blankvalue comparison",
			filterExpr: `BLANKVALUE(Account.Industry, "Fallback") == "Fallback"`,
		},
		{
			name:       "nullvalue comparison",
			filterExpr: `NULLVALUE(Account.Industry, "Fallback") == "Fallback"`,
		},
		{
			name:       "literal side comparison",
			filterExpr: `"Fallback" == BLANKVALUE(Account.Industry, "Fallback")`,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := parentFilterQueryService(repo,
				parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}}),
				parentFilterAccountSchema(models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString}),
			)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
				Filter:        queryspec.FormulaPredicate(tc.filterExpr),
			}

			if _, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"}); err != nil {
				t.Fatalf("QueryWithSpec error = %v, want accepted direct parent-field coalescing filter", err)
			}
			if !repo.findCalled {
				t.Fatal("repo.Find was not called for an accepted parent-field coalescing filter")
			}
			assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, *defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"))
		})
	}
}

func TestQueryParentFilterRejectsUnsupportedAdjacentShapes(t *testing.T) {
	for _, tc := range []struct {
		name       string
		filterExpr string
		want       string
	}{
		{
			name:       "root plus parent mixed predicate",
			filterExpr: "Account.Industry = StageName",
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent blankvalue helper with non-literal fallback",
			filterExpr: `BLANKVALUE(Account.Industry, StageName) == "Fallback"`,
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent blankvalue helper with parent fallback",
			filterExpr: `BLANKVALUE(Account.Industry, Account.Rating) == "Fallback"`,
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent blankvalue helper compared to nil",
			filterExpr: `BLANKVALUE(Account.Industry, "Fallback") == nil`,
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent blankvalue helper with nil fallback compared to nil",
			filterExpr: `BLANKVALUE(Account.Industry, nil) == nil`,
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent nullvalue helper compared to nil",
			filterExpr: `NULLVALUE(Account.Industry, "Fallback") == nil`,
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent in non-literal list",
			filterExpr: "Account.Industry in [StageName]",
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent in empty list",
			filterExpr: "Account.Industry in []",
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent in nil list",
			filterExpr: "Account.Industry in [nil]",
			want:       "unsupported relationship field filter shape",
		},
		{
			name:       "parent contains non-string literal",
			filterExpr: "Account.Industry contains 42",
			want:       "contains operator requires string literal on right side",
		},
		{
			name:       "parent endsWith non-string literal",
			filterExpr: "Account.Industry endsWith 42",
			want:       "endsWith operator requires string literal on right side",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := parentFilterQueryService(repo,
				parentFilterRootSchema(
					models.FieldMetadata{APIName: "StageName", Type: constants.FieldTypeString},
					models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
				),
				parentFilterAccountSchema(models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString}),
			)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
				Filter:        queryspec.FormulaPredicate(tc.filterExpr),
			}

			_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("Query error = %v, want %q", err, tc.want)
			}
			if repo.findCalled {
				t.Fatal("repo.Find was called after an unsupported parent-field filter")
			}
		})
	}
}

func TestQueryParentFilterExactComparisonBuildsExecutionPlan(t *testing.T) {
	root := &models.ObjectMetadata{
		APIName:      "Opportunity",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}, PhysicalName: "acct_id"},
		},
	}
	target := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPublicRead,
		ReadSource:   &models.ObjectReadSourceConfig{Kind: "physical_table", PhysicalTable: "crm_account"},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true, PhysicalName: "account_pk"},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, IsSystem: true, PhysicalName: "deleted_flag"},
			{APIName: "Industry", Type: constants.FieldTypeString, PhysicalName: "industry_code"},
		},
	}
	repo := &clauseFLSRepository{}
	qs := parentFilterQueryService(repo, root, target)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
	}

	if _, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"}); err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted exact parent-field filter", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:         "Account.Industry",
		relationship:    "Account",
		rootFK:          "AccountId",
		rootFKPhysical:  "acct_id",
		targetObject:    "Account",
		targetTable:     "crm_account",
		targetID:        "account_pk",
		targetField:     "Industry",
		targetPhysical:  "industry_code",
		targetIsDeleted: "deleted_flag",
	})
}

func TestQueryParentFilterPrivateOwnerTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	qs := parentFilterQueryServiceWithAccess(
		repo,
		parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}}),
		nil,
		nil,
		nil,
		&models.ObjectMetadata{
			APIName:      "Account",
			SharingModel: constants.SharingModelPrivate,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
				{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id"},
				{APIName: "Industry", Type: constants.FieldTypeString},
			},
		},
	)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted private parent-field filter with owner visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                       "Account.Industry",
		relationship:                  "Account",
		rootFK:                        "AccountId",
		rootFKPhysical:                "AccountId",
		targetObject:                  "Account",
		targetTable:                   "Account",
		targetID:                      constants.FieldID,
		targetField:                   "Industry",
		targetPhysical:                "Industry",
		targetVisibilityOwnerPhysical: "owner_id",
		targetVisibilityOwnerIDs:      []string{"005-reader"},
	})
}

func TestQueryParentFilterDenyAllTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	qs := parentFilterQueryServiceWithAccess(
		repo,
		parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}}),
		nil,
		nil,
		nil,
		&models.ObjectMetadata{
			APIName:      "Account",
			SharingModel: constants.SharingModelPrivate,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
				{APIName: "Industry", Type: constants.FieldTypeString},
			},
		},
	)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted private parent-field filter with deny-all visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                 "Account.Industry",
		relationship:            "Account",
		rootFK:                  "AccountId",
		rootFKPhysical:          "AccountId",
		targetObject:            "Account",
		targetTable:             "Account",
		targetID:                constants.FieldID,
		targetField:             "Industry",
		targetPhysical:          "Industry",
		targetVisibilityDenyAll: true,
	})
}

func TestQueryParentFilterAllowedIDTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "ReportId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Report"}})
	target := &models.ObjectMetadata{
		APIName:      "Report",
		SharingModel: constants.SharingModelFolderContentSharing,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "FolderId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ReportFolder"}, PhysicalName: "folder_id"},
			{APIName: "Name", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("Report"): {
				ObjectAPIName:    "Report",
				AllowedIDs:       []string{"00l-folder-a", "00l-folder-b"},
				AllowedIDsColumn: "FolderId",
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Report.Name = 'Pipeline'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted parent-field filter with allowed-id visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                           "Report.Name",
		relationship:                      "Report",
		rootFK:                            "ReportId",
		rootFKPhysical:                    "ReportId",
		targetObject:                      "Report",
		targetTable:                       "Report",
		targetID:                          constants.FieldID,
		targetField:                       "Name",
		targetPhysical:                    "Name",
		targetVisibilityAllowedIDPhysical: "folder_id",
		targetVisibilityAllowedIDs:        []string{"00l-folder-a", "00l-folder-b"},
	})
}

func TestQueryParentFilterOwnerVisibilityTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "ViewId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ListView"}})
	target := &models.ObjectMetadata{
		APIName:      "ListView",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:                "ownerVisibility",
			OwnerField:          "OwnerId",
			VisibilityField:     "Visibility",
			OwnerVisibleValues:  []string{"Private"},
			PublicVisibleValues: []string{"Public"},
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id", IsSystem: true},
			{APIName: "Visibility", Type: constants.FieldTypeString, PhysicalName: "visibility_state"},
			{APIName: "Name", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target}}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("View.Name = 'Pipeline'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted parent-field filter with owner-visibility row policy", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "View.Name",
		relationship:   "View",
		rootFK:         "ViewId",
		rootFKPhysical: "ViewId",
		targetObject:   "ListView",
		targetTable:    "ListView",
		targetID:       constants.FieldID,
		targetField:    "Name",
		targetPhysical: "Name",
		targetVisibilityOwnerVisibilityOwnerPhysical:      "owner_id",
		targetVisibilityOwnerVisibilityVisibilityPhysical: "visibility_state",
		targetVisibilityOwnerVisibilityUserID:             "005-reader",
		targetVisibilityOwnerVisibilityOwnerValues:        []string{"Private"},
		targetVisibilityOwnerVisibilityPublicValues:       []string{"Public"},
	})
}

func TestQueryParentFilterTargetRecordTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
	target := &models.ObjectMetadata{
		APIName:      "HistoryLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: "TargetObjectType",
			TargetRecordField: "TargetRecordId",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
			{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	referenced := &models.ObjectMetadata{
		APIName:      "GenericObject",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, PhysicalName: "is_deleted", IsSystem: true},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target, referenced}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("HistoryLike"): {
				ObjectAPIName: "HistoryLike",
				TargetRecordFilters: []domainrls.TargetRecordFilter{{
					TargetObjectAPIName: "GenericObject",
					TargetObjectField:   "TargetObjectType",
					TargetRecordField:   "TargetRecordId",
					TargetSchema:        referenced,
					TargetRLSResult:     &domainrls.Result{Skip: true, ObjectAPIName: "GenericObject"},
				}},
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted parent-field filter with public target-record visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "History.Subject",
		relationship:   "History",
		rootFK:         "HistoryId",
		rootFKPhysical: "HistoryId",
		targetObject:   "HistoryLike",
		targetTable:    "HistoryLike",
		targetID:       constants.FieldID,
		targetField:    "Subject",
		targetPhysical: "Subject",
		targetVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
			ReferencedObjectAPIName:         "GenericObject",
			ObjectFieldPhysicalName:         "target_object_type",
			RecordFieldPhysicalName:         "target_record_id",
			ReferencedTable:                 "GenericObject",
			ReferencedIDPhysicalName:        constants.FieldID,
			ReferencedHasIsDeleted:          true,
			ReferencedIsDeletedPhysicalName: "is_deleted",
		}},
	})
}

func TestQueryParentFilterTargetRecordTargetPreservesEmptyGroupedAssociation(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "ActivityId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ActivityLike"}})
	target := &models.ObjectMetadata{
		APIName:      "ActivityLike",
		SharingModel: constants.SharingModelPrivate,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind: "targetRecordRead",
			TargetAssociations: []models.RowReadTargetAssociation{
				{TargetObjectField: "WhoType", TargetRecordField: "WhoId"},
				{TargetObjectField: "WhatType", TargetRecordField: "WhatId"},
			},
			IncludeSourceVisibility: true,
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "WhoType", Type: constants.FieldTypeString, PhysicalName: "who_type"},
			{APIName: "WhoId", Type: constants.FieldTypeString, PhysicalName: "who_id"},
			{APIName: "WhatType", Type: constants.FieldTypeString, PhysicalName: "what_type"},
			{APIName: "WhatId", Type: constants.FieldTypeString, PhysicalName: "what_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	referenced := &models.ObjectMetadata{
		APIName:      "PersonLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true}},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target, referenced}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("ActivityLike"): {
				ObjectAPIName: "ActivityLike",
				TargetRecordFilters: []domainrls.TargetRecordFilter{
					{
						TargetObjectField: "WhoType",
						TargetRecordField: "WhoId",
						Alternatives: []domainrls.TargetRecordFilterAlternative{{
							TargetObjectAPIName: "PersonLike",
							TargetSchema:        referenced,
							TargetRLSResult:     &domainrls.Result{Skip: true, ObjectAPIName: "PersonLike"},
						}},
					},
					{
						TargetObjectField: "WhatType",
						TargetRecordField: "WhatId",
						Alternatives:      []domainrls.TargetRecordFilterAlternative{},
					},
				},
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Activity.Subject = 'Call'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want an accepted fail-closed relationship plan", err)
	}
	if !repo.findCalled || len(repo.findReq.ParentFilterPaths) != 1 {
		t.Fatalf("repo.Find parent paths = %#v, want one relationship plan", repo.findReq.ParentFilterPaths)
	}
	filters := repo.findReq.ParentFilterPaths[0].TargetVisibilityTargetRecordFilters
	if len(filters) != 2 {
		t.Fatalf("target-record plans = %#v, want both association groups", filters)
	}
	if filters[1].Alternatives == nil || len(filters[1].Alternatives) != 0 {
		t.Fatalf("unreadable What plan alternatives = %#v, want an explicit empty group", filters[1].Alternatives)
	}
}

func TestQueryParentFilterMalformedGroupedTargetRecordPolicyDeniesAll(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "ActivityId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ActivityLike"}})
	target := &models.ObjectMetadata{
		APIName:      "ActivityLike",
		SharingModel: constants.SharingModelPrivate,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind: "targetRecordRead",
			TargetAssociations: []models.RowReadTargetAssociation{
				{TargetObjectField: "WhoType", TargetRecordField: "WhoId"},
				{TargetRecordField: "WhatId"},
			},
			IncludeSourceVisibility: true,
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "WhoType", Type: constants.FieldTypeString, PhysicalName: "who_type"},
			{APIName: "WhoId", Type: constants.FieldTypeString, PhysicalName: "who_id"},
			{APIName: "WhatId", Type: constants.FieldTypeString, PhysicalName: "what_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	referenced := &models.ObjectMetadata{
		APIName:      "PersonLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields:       []models.FieldMetadata{{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true}},
	}
	qs := parentFilterQueryService(repo, root, target, referenced)
	ctx := constants.WithStandardActionInternalAccessScopes(context.Background(), "activity-related-read", []constants.StandardActionInternalAccessScope{{
		StandardActionID: "activity-related-read",
		ObjectAPIName:    "ActivityLike",
		Operation:        constants.PermRead,
	}})
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Activity.Subject = 'Call'"),
	}

	_, err := qs.QueryWithSpec(ctx, req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want an accepted deny-all relationship plan", err)
	}
	if !repo.findCalled || len(repo.findReq.ParentFilterPaths) != 1 {
		t.Fatalf("repo.Find parent paths = %#v, want one relationship plan", repo.findReq.ParentFilterPaths)
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                 "Activity.Subject",
		relationship:            "Activity",
		rootFK:                  "ActivityId",
		rootFKPhysical:          "ActivityId",
		targetObject:            "ActivityLike",
		targetTable:             "ActivityLike",
		targetID:                constants.FieldID,
		targetField:             "Subject",
		targetPhysical:          "Subject",
		targetVisibilityDenyAll: true,
	})
}

func TestQueryParentFilterTargetRecordTargetPreservesPrivateReferencedVisibility(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
	target := &models.ObjectMetadata{
		APIName:      "HistoryLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: "TargetObjectType",
			TargetRecordField: "TargetRecordId",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
			{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	referenced := &models.ObjectMetadata{
		APIName:      "PrivateObject",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id", IsSystem: true},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target, referenced}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("HistoryLike"): {
				ObjectAPIName: "HistoryLike",
				TargetRecordFilters: []domainrls.TargetRecordFilter{{
					TargetObjectAPIName: "PrivateObject",
					TargetObjectField:   "TargetObjectType",
					TargetRecordField:   "TargetRecordId",
					TargetSchema:        referenced,
					TargetRLSResult: &domainrls.Result{
						ObjectAPIName: "PrivateObject",
						HasOwnerField: true,
						UserID:        "005-reader",
						OwnerUserIDs:  []string{"005-reader"},
					},
				}},
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted parent-field filter with private target-record visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "History.Subject",
		relationship:   "History",
		rootFK:         "HistoryId",
		rootFKPhysical: "HistoryId",
		targetObject:   "HistoryLike",
		targetTable:    "HistoryLike",
		targetID:       constants.FieldID,
		targetField:    "Subject",
		targetPhysical: "Subject",
		targetVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
			ReferencedObjectAPIName:               "PrivateObject",
			ObjectFieldPhysicalName:               "target_object_type",
			RecordFieldPhysicalName:               "target_record_id",
			ReferencedTable:                       "PrivateObject",
			ReferencedIDPhysicalName:              constants.FieldID,
			ReferencedVisibilityOwnerPhysicalName: "owner_id",
			ReferencedVisibilityOwnerUserIDs:      []string{"005-reader"},
		}},
	})
}

func TestQueryParentFilterTargetRecordTargetPreservesNestedReferencedVisibility(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
	target := &models.ObjectMetadata{
		APIName:      "HistoryLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: "TargetObjectType",
			TargetRecordField: "TargetRecordId",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
			{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	nested := &models.ObjectMetadata{
		APIName:      "AuditLink",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "NestedObjectType", Type: constants.FieldTypeString, PhysicalName: "nested_object_type"},
			{APIName: "NestedRecordId", Type: constants.FieldTypeString, PhysicalName: "nested_record_id"},
		},
	}
	referenced := &models.ObjectMetadata{
		APIName:      "PrivateObject",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id", IsSystem: true},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target, nested, referenced}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("HistoryLike"): {
				ObjectAPIName: "HistoryLike",
				TargetRecordFilters: []domainrls.TargetRecordFilter{{
					TargetObjectAPIName: "AuditLink",
					TargetObjectField:   "TargetObjectType",
					TargetRecordField:   "TargetRecordId",
					TargetSchema:        nested,
					TargetRLSResult: &domainrls.Result{
						ObjectAPIName: "AuditLink",
						TargetRecordFilters: []domainrls.TargetRecordFilter{{
							TargetObjectAPIName: "PrivateObject",
							TargetObjectField:   "NestedObjectType",
							TargetRecordField:   "NestedRecordId",
							TargetSchema:        referenced,
							TargetRLSResult: &domainrls.Result{
								ObjectAPIName: "PrivateObject",
								HasOwnerField: true,
								UserID:        "005-reader",
								OwnerUserIDs:  []string{"005-reader"},
							},
						}},
					},
				}},
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted parent-field filter with nested target-record visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "History.Subject",
		relationship:   "History",
		rootFK:         "HistoryId",
		rootFKPhysical: "HistoryId",
		targetObject:   "HistoryLike",
		targetTable:    "HistoryLike",
		targetID:       constants.FieldID,
		targetField:    "Subject",
		targetPhysical: "Subject",
		targetVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
			ReferencedObjectAPIName:  "AuditLink",
			ObjectFieldPhysicalName:  "target_object_type",
			RecordFieldPhysicalName:  "target_record_id",
			ReferencedTable:          "AuditLink",
			ReferencedIDPhysicalName: constants.FieldID,
			ReferencedVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
				ReferencedObjectAPIName:               "PrivateObject",
				ObjectFieldPhysicalName:               "nested_object_type",
				RecordFieldPhysicalName:               "nested_record_id",
				ReferencedTable:                       "PrivateObject",
				ReferencedIDPhysicalName:              constants.FieldID,
				ReferencedVisibilityOwnerPhysicalName: "owner_id",
				ReferencedVisibilityOwnerUserIDs:      []string{"005-reader"},
			}},
		}},
	})
}

func TestQueryParentFilterTargetRecordTargetRejectsVisibilityCycle(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
	target := &models.ObjectMetadata{
		APIName:      "HistoryLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: "TargetObjectType",
			TargetRecordField: "TargetRecordId",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
			{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("HistoryLike"): {
				ObjectAPIName: "HistoryLike",
				TargetRecordFilters: []domainrls.TargetRecordFilter{{
					TargetObjectAPIName: "HistoryLike",
					TargetObjectField:   "TargetObjectType",
					TargetRecordField:   "TargetRecordId",
					TargetSchema:        target,
					TargetRLSResult: &domainrls.Result{
						ObjectAPIName: "HistoryLike",
						TargetRecordFilters: []domainrls.TargetRecordFilter{{
							TargetObjectAPIName: "HistoryLike",
							TargetObjectField:   "TargetObjectType",
							TargetRecordField:   "TargetRecordId",
							TargetSchema:        target,
							TargetRLSResult:     &domainrls.Result{Skip: true, ObjectAPIName: "HistoryLike"},
						}},
					},
				}},
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err == nil || !strings.Contains(err.Error(), "cycle") {
		t.Fatalf("QueryWithSpec error = %v, want target-record visibility cycle rejection", err)
	}
	if repo.findCalled {
		t.Fatal("repo.Find was called after a target-record visibility cycle")
	}
}

func TestQueryParentFilterTargetRecordVisibilityRejectsOverMaxDepth(t *testing.T) {
	schemas := make([]*models.ObjectMetadata, queryParentFilterTargetRecordVisibilityMaxDepth+2)
	for i := range schemas {
		schemas[i] = &models.ObjectMetadata{
			APIName:      fmt.Sprintf("TargetLevel%d", i),
			SharingModel: constants.SharingModelPublicReadWrite,
			Fields: []models.FieldMetadata{
				{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
				{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
				{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			},
		}
	}
	result := &domainrls.Result{ObjectAPIName: schemas[0].APIName}
	current := result
	for i := 1; i < len(schemas); i++ {
		next := &domainrls.Result{ObjectAPIName: schemas[i].APIName}
		if i == len(schemas)-1 {
			next.Skip = true
		}
		current.TargetRecordFilters = []domainrls.TargetRecordFilter{{
			TargetObjectAPIName: schemas[i].APIName,
			TargetObjectField:   "TargetObjectType",
			TargetRecordField:   "TargetRecordId",
			TargetSchema:        schemas[i],
			TargetRLSResult:     next,
		}}
		current = next
	}

	_, err := queryParentFilterVisibilityPlanFromRLS(schemas[0], result, "FilterExpr", schemas[0].APIName, false, queryParentFilterTargetRecordVisibilityMaxDepth, make(map[string]bool))
	if err == nil || !strings.Contains(err.Error(), "bounded target-record visibility") {
		t.Fatalf("queryParentFilterVisibilityPlanFromRLS error = %v, want bounded depth rejection", err)
	}
}

func TestQueryParentFilterTargetRecordTargetScanAllIncludesDeletedReferencedRecords(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
	target := &models.ObjectMetadata{
		APIName:      "HistoryLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: "TargetObjectType",
			TargetRecordField: "TargetRecordId",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
			{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	referenced := &models.ObjectMetadata{
		APIName:      "GenericObject",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, PhysicalName: "is_deleted", IsSystem: true},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target, referenced}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("HistoryLike"): {
				ObjectAPIName: "HistoryLike",
				TargetRecordFilters: []domainrls.TargetRecordFilter{{
					TargetObjectAPIName: "GenericObject",
					TargetObjectField:   "TargetObjectType",
					TargetRecordField:   "TargetRecordId",
					TargetSchema:        referenced,
					TargetRLSResult:     &domainrls.Result{Skip: true, ObjectAPIName: "GenericObject"},
				}},
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
		ScanAll:       true,
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted parent-field filter with queryAll target-record visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "History.Subject",
		relationship:   "History",
		rootFK:         "HistoryId",
		rootFKPhysical: "HistoryId",
		targetObject:   "HistoryLike",
		targetTable:    "HistoryLike",
		targetID:       constants.FieldID,
		targetField:    "Subject",
		targetPhysical: "Subject",
		targetVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
			ReferencedObjectAPIName:         "GenericObject",
			ObjectFieldPhysicalName:         "target_object_type",
			RecordFieldPhysicalName:         "target_record_id",
			ReferencedTable:                 "GenericObject",
			ReferencedIDPhysicalName:        constants.FieldID,
			ReferencedHasIsDeleted:          true,
			ReferencedIncludeDeleted:        true,
			ReferencedIsDeletedPhysicalName: "is_deleted",
		}},
	})
}

func TestQueryParentFilterTargetRecordTargetSystemContextPreservesDelegatedVisibility(t *testing.T) {
	for _, tc := range []struct {
		name           string
		scanAll        bool
		includeDeleted bool
	}{
		{name: "query", scanAll: false, includeDeleted: false},
		{name: "queryAll", scanAll: true, includeDeleted: true},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
			target := &models.ObjectMetadata{
				APIName:      "HistoryLike",
				SharingModel: constants.SharingModelPublicReadWrite,
				AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
					Kind:              "targetRecordRead",
					TargetObjectField: "TargetObjectType",
					TargetRecordField: "TargetRecordId",
				}},
				Fields: []models.FieldMetadata{
					{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
					{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
					{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
					{APIName: "Subject", Type: constants.FieldTypeString},
				},
			}
			referenced := &models.ObjectMetadata{
				APIName:      "GenericObject",
				SharingModel: constants.SharingModelPublicReadWrite,
				Fields: []models.FieldMetadata{
					{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
					{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, PhysicalName: "is_deleted", IsSystem: true},
				},
			}
			metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target, referenced}}, nil, nil)
			permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{
				objectReads: map[string]bool{
					strings.ToLower("GenericObject"): true,
					strings.ToLower("Opportunity"):   false,
				},
			}, metadataSvc, nil)
			qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
				Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
				ScanAll:       tc.scanAll,
			}
			ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "test target-record parent filter")

			_, err := qs.QueryWithSpec(ctx, req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if err != nil {
				t.Fatalf("QueryWithSpec error = %v, want accepted SystemContext parent-field filter with target-record visibility", err)
			}
			if !repo.findCalled {
				t.Fatal("repo.Find was not called")
			}
			assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
				rawPath:        "History.Subject",
				relationship:   "History",
				rootFK:         "HistoryId",
				rootFKPhysical: "HistoryId",
				targetObject:   "HistoryLike",
				targetTable:    "HistoryLike",
				targetID:       constants.FieldID,
				targetField:    "Subject",
				targetPhysical: "Subject",
				targetVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
					ReferencedObjectAPIName:  "Opportunity",
					ObjectFieldPhysicalName:  "target_object_type",
					RecordFieldPhysicalName:  "target_record_id",
					ReferencedTable:          "Opportunity",
					ReferencedIDPhysicalName: constants.FieldID,
					ReferencedIncludeDeleted: tc.includeDeleted,
				}, {
					ReferencedObjectAPIName:         "GenericObject",
					ObjectFieldPhysicalName:         "target_object_type",
					RecordFieldPhysicalName:         "target_record_id",
					ReferencedTable:                 "GenericObject",
					ReferencedIDPhysicalName:        constants.FieldID,
					ReferencedHasIsDeleted:          true,
					ReferencedIncludeDeleted:        tc.includeDeleted,
					ReferencedIsDeletedPhysicalName: "is_deleted",
				}},
			})
		})
	}
}

func TestQueryParentFilterTargetRecordTargetSystemContextIgnoresUserTargetObjectDenial(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
	target := &models.ObjectMetadata{
		APIName:      "HistoryLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: "TargetObjectType",
			TargetRecordField: "TargetRecordId",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
			{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	referenced := &models.ObjectMetadata{
		APIName:      "GenericObject",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, PhysicalName: "is_deleted", IsSystem: true},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target, referenced}}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{
		objectReads: map[string]bool{
			strings.ToLower("GenericObject"): false,
			strings.ToLower("Opportunity"):   false,
		},
	}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
	}
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "test target-record parent filter")

	_, err := qs.QueryWithSpec(ctx, req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted SystemContext parent-field filter despite user target-object denial", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "History.Subject",
		relationship:   "History",
		rootFK:         "HistoryId",
		rootFKPhysical: "HistoryId",
		targetObject:   "HistoryLike",
		targetTable:    "HistoryLike",
		targetID:       constants.FieldID,
		targetField:    "Subject",
		targetPhysical: "Subject",
		targetVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
			ReferencedObjectAPIName:  "Opportunity",
			ObjectFieldPhysicalName:  "target_object_type",
			RecordFieldPhysicalName:  "target_record_id",
			ReferencedTable:          "Opportunity",
			ReferencedIDPhysicalName: constants.FieldID,
		}, {
			ReferencedObjectAPIName:         "GenericObject",
			ObjectFieldPhysicalName:         "target_object_type",
			RecordFieldPhysicalName:         "target_record_id",
			ReferencedTable:                 "GenericObject",
			ReferencedIDPhysicalName:        constants.FieldID,
			ReferencedHasIsDeleted:          true,
			ReferencedIsDeletedPhysicalName: "is_deleted",
		}},
	})
}

func TestQueryParentFilterTargetRecordTargetSystemContextSkipsUnsupportedTargetShapes(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "HistoryId", Type: constants.FieldTypeReference, ReferenceTo: []string{"HistoryLike"}})
	target := &models.ObjectMetadata{
		APIName:      "HistoryLike",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:              "targetRecordRead",
			TargetObjectField: "TargetObjectType",
			TargetRecordField: "TargetRecordId",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "TargetObjectType", Type: constants.FieldTypeString, PhysicalName: "target_object_type"},
			{APIName: "TargetRecordId", Type: constants.FieldTypeString, PhysicalName: "target_record_id"},
			{APIName: "Subject", Type: constants.FieldTypeString},
		},
	}
	publicReferenced := &models.ObjectMetadata{
		APIName:      "GenericObject",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, PhysicalName: "is_deleted", IsSystem: true},
		},
	}
	privateReferenced := &models.ObjectMetadata{
		APIName:      "PrivateObject",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, IsSystem: true},
		},
	}
	ownerVisibilityReferenced := &models.ObjectMetadata{
		APIName:      "OwnerVisibleObject",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:            "ownerVisibility",
			OwnerField:      constants.FieldOwnerID,
			VisibilityField: "Visibility",
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, IsSystem: true},
			{APIName: "Visibility", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{
		schemas: []*models.ObjectMetadata{root, target, publicReferenced, privateReferenced, ownerVisibilityReferenced},
	}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("History.Subject = 'Call'"),
	}
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "test target-record parent filter")

	_, err := qs.QueryWithSpec(ctx, req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted SystemContext parent-field filter with supported public target only", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "History.Subject",
		relationship:   "History",
		rootFK:         "HistoryId",
		rootFKPhysical: "HistoryId",
		targetObject:   "HistoryLike",
		targetTable:    "HistoryLike",
		targetID:       constants.FieldID,
		targetField:    "Subject",
		targetPhysical: "Subject",
		targetVisibilityTargetRecordFilters: []models.QueryParentFilterTargetRecordVisibility{{
			ReferencedObjectAPIName:  "Opportunity",
			ObjectFieldPhysicalName:  "target_object_type",
			RecordFieldPhysicalName:  "target_record_id",
			ReferencedTable:          "Opportunity",
			ReferencedIDPhysicalName: constants.FieldID,
		}, {
			ReferencedObjectAPIName:         "GenericObject",
			ObjectFieldPhysicalName:         "target_object_type",
			RecordFieldPhysicalName:         "target_record_id",
			ReferencedTable:                 "GenericObject",
			ReferencedIDPhysicalName:        constants.FieldID,
			ReferencedHasIsDeleted:          true,
			ReferencedIsDeletedPhysicalName: "is_deleted",
		}},
	})
}

func TestQueryParentFilterManualShareTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}})
	target := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id", IsSystem: true},
			{APIName: "Industry", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target}}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{
		manualShareRecordIDs: map[string][]string{"account": {"001-share"}},
	}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted private parent-field filter with manual-share visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                           "Account.Industry",
		relationship:                      "Account",
		rootFK:                            "AccountId",
		rootFKPhysical:                    "AccountId",
		targetObject:                      "Account",
		targetTable:                       "Account",
		targetID:                          constants.FieldID,
		targetField:                       "Industry",
		targetPhysical:                    "Industry",
		targetVisibilityOwnerPhysical:     "owner_id",
		targetVisibilityOwnerIDs:          []string{"005-reader"},
		targetVisibilityManualShareUserID: "005-reader",
	})
}

func TestQueryParentFilterTeamMemberTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}})
	target := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id", IsSystem: true},
			{APIName: "Industry", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target}}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{
		teamMemberRecordIDs: map[string][]string{"account": {"001-team"}},
	}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted private parent-field filter with team-member visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                          "Account.Industry",
		relationship:                     "Account",
		rootFK:                           "AccountId",
		rootFKPhysical:                   "AccountId",
		targetObject:                     "Account",
		targetTable:                      "Account",
		targetID:                         constants.FieldID,
		targetField:                      "Industry",
		targetPhysical:                   "Industry",
		targetVisibilityOwnerPhysical:    "owner_id",
		targetVisibilityOwnerIDs:         []string{"005-reader"},
		targetVisibilityTeamMemberUserID: "005-reader",
	})
}

func TestQueryParentFilterSharingRuleTargetBuildsVisibilityPlan(t *testing.T) {
	sourceGroupID := "00G-source"
	shareWithGroupID := "00G-reader"
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}})
	target := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id", IsSystem: true},
			{APIName: "Industry", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{
		schemas: []*models.ObjectMetadata{root, target},
		sharingRules: map[string][]*models.SharingRule{
			strings.ToLower("Account"): {
				{
					ID:                "sr-account-source-group",
					ObjectAPIName:     "Account",
					AccessLevel:       constants.PermRead,
					SharedFromGroupID: &sourceGroupID,
					ShareWithGroupID:  &shareWithGroupID,
				},
			},
		},
	}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{
		userGroupIDs:   map[string][]string{"005-reader": {"00G-reader"}},
		groupMemberIDs: map[string][]string{"00G-source": {"005-source-member"}},
	}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted private parent-field filter with sharing-rule visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                                 "Account.Industry",
		relationship:                            "Account",
		rootFK:                                  "AccountId",
		rootFKPhysical:                          "AccountId",
		targetObject:                            "Account",
		targetTable:                             "Account",
		targetID:                                constants.FieldID,
		targetField:                             "Industry",
		targetPhysical:                          "Industry",
		targetVisibilityOwnerPhysical:           "owner_id",
		targetVisibilityOwnerIDs:                []string{"005-reader"},
		targetVisibilityGroupOwnerIDs:           []string{"00G-reader"},
		targetVisibilitySharingRuleFromGroupIDs: []string{"00G-source"},
	})
}

func TestQueryParentFilterDerivedTargetUsesSupportedRLSPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}})
	target := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		AccessPolicy: &models.ObjectAccessPolicy{
			Mode:       "derived",
			ObjectRead: &models.AccessPolicyExpr{Kind: "systemPermission", APIName: "ReadDerivedAccount"},
			FieldRead:  &models.AccessPolicyExpr{Kind: "systemPermission", APIName: "ReadDerivedAccount"},
			RowRead: &models.RowReadPolicy{
				Kind:               "ownerVisibility",
				OwnerField:         constants.FieldOwnerID,
				VisibilityField:    "Visibility",
				OwnerVisibleValues: []string{"Private"},
			},
		},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, PhysicalName: "owner_id", IsSystem: true},
			{APIName: "Visibility", Type: constants.FieldTypeString, PhysicalName: "visibility_state"},
			{APIName: "Industry", Type: constants.FieldTypeString},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target}}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{
		systemPerms: map[string]bool{"ReadDerivedAccount": true},
	}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, permissionSvc, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted derived parent target with supported RLS visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:        "Account.Industry",
		relationship:   "Account",
		rootFK:         "AccountId",
		rootFKPhysical: "AccountId",
		targetObject:   "Account",
		targetTable:    "Account",
		targetID:       constants.FieldID,
		targetField:    "Industry",
		targetPhysical: "Industry",
		targetVisibilityOwnerVisibilityOwnerPhysical:      "owner_id",
		targetVisibilityOwnerVisibilityVisibilityPhysical: "visibility_state",
		targetVisibilityOwnerVisibilityUserID:             "005-reader",
		targetVisibilityOwnerVisibilityOwnerValues:        []string{"Private"},
	})
}

func TestQueryParentFilterCriteriaTargetBuildsVisibilityPlan(t *testing.T) {
	repo := &clauseFLSRepository{}
	root := parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}})
	target := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPrivate,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Industry", Type: constants.FieldTypeString, PhysicalName: "industry"},
			{APIName: "Rating", Type: constants.FieldTypeString, PhysicalName: "rating"},
		},
	}
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: []*models.ObjectMetadata{root, target}}, nil, nil)
	basePermissions := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(repo, metadataSvc, &parentFilterRLSOverridePermissionChecker{
		PermissionService: basePermissions,
		results: map[string]*domainrls.Result{
			strings.ToLower("Account"): {
				ObjectAPIName:        "Account",
				CriteriaExpressions:  []string{"Industry == 'Technology'"},
				CriteriaSQLFragments: []string{"`Industry` = 'Technology'"},
			},
		},
	}, nil)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Rating = 'Hot'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted private parent-field filter with criteria visibility", err)
	}
	if !repo.findCalled {
		t.Fatal("repo.Find was not called")
	}
	assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, parentFilterPlanAssertion{
		rawPath:                             "Account.Rating",
		relationship:                        "Account",
		rootFK:                              "AccountId",
		rootFKPhysical:                      "AccountId",
		targetObject:                        "Account",
		targetTable:                         "Account",
		targetID:                            constants.FieldID,
		targetField:                         "Rating",
		targetPhysical:                      "rating",
		targetVisibilityCriteriaExpressions: []string{"Industry == 'Technology'"},
		targetVisibilityCriteriaFields:      []models.QueryParentFilterCriteriaField{{APIName: "Industry", PhysicalName: "industry"}},
	})
}

func TestQueryParentFilterAdmissionRejectsUnsafeExecutionGates(t *testing.T) {
	for _, tc := range []struct {
		name        string
		root        *models.ObjectMetadata
		target      *models.ObjectMetadata
		objectReads map[string]bool
		fieldReads  map[string]bool
		want        string
	}{
		{
			name: "parent object read denied even when target FLS allows field",
			objectReads: map[string]bool{
				strings.ToLower("Opportunity"): true,
				strings.ToLower("Account"):     false,
			},
			fieldReads: map[string]bool{
				strings.ToLower("Opportunity.AccountId"): true,
				strings.ToLower("Account.Industry"):      true,
			},
			want: "read",
		},
		{
			name: "view-only root relationship field",
			root: parentFilterRootSchema(models.FieldMetadata{
				APIName:          "AccountId",
				Type:             constants.FieldTypeReference,
				ReferenceTo:      []string{"Account"},
				ProjectionConfig: &models.FieldProjectionConfig{Kind: "lookup", TargetObject: "Account", SelectField: constants.FieldID},
			}),
			want: "physical root relationship field",
		},
		{
			name: "root lookup FLS denied",
			fieldReads: map[string]bool{
				strings.ToLower("Opportunity.AccountId"): false,
			},
			want: "AccountId",
		},
		{
			name: "target leaf FLS denied",
			fieldReads: map[string]bool{
				strings.ToLower("Account.Industry"): false,
			},
			want: "Industry",
		},
		{
			name: "malformed target-record row-read policy parent target",
			target: &models.ObjectMetadata{
				APIName:      "Account",
				SharingModel: constants.SharingModelPublicReadWrite,
				AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
					Kind:              "targetRecordRead",
					TargetObjectField: "TargetObjectType",
					TargetRecordField: "TargetObjectId",
				}},
				Fields: []models.FieldMetadata{
					{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
					{APIName: "Industry", Type: constants.FieldTypeString},
				},
			},
			want: "parent target-record object field",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			root := tc.root
			if root == nil {
				root = parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}})
			}
			target := tc.target
			if target == nil {
				target = parentFilterAccountSchema(models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString})
			}
			repo := &clauseFLSRepository{}
			qs := parentFilterQueryServiceWithAccess(repo, root, nil, tc.objectReads, tc.fieldReads, target)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
				Filter:        queryspec.FormulaPredicate("Account.Industry = 'Technology'"),
			}

			_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("Query error = %v, want %q", err, tc.want)
			}
			if repo.findCalled {
				t.Fatal("repo.Find was called after unsafe parent-field filter admission")
			}
		})
	}
}

func TestQueryParentFilterRejectsCompoundTargetWhenMemberFLSDenied(t *testing.T) {
	repo := &clauseFLSRepository{}
	qs := parentFilterQueryServiceWithAccess(
		repo,
		parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}}),
		nil,
		nil,
		map[string]bool{
			strings.ToLower("Account.FirstName"): false,
		},
		parentFilterAccountSchema(
			models.FieldMetadata{APIName: "Name", Type: constants.FieldTypeString, IsVirtual: true, VirtualKind: constants.VirtualFieldCompound, IsCompound: true, ComposedFrom: []string{"FirstName", "LastName"}},
			models.FieldMetadata{APIName: "FirstName", Type: constants.FieldTypeString, PhysicalName: "first_name"},
			models.FieldMetadata{APIName: "LastName", Type: constants.FieldTypeString, PhysicalName: "last_name"},
		),
	)
	req := queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{constants.FieldID},
		Filter:        queryspec.FormulaPredicate("Account.Name = 'Ada Lovelace'"),
	}

	_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err == nil || !strings.Contains(err.Error(), "FirstName") {
		t.Fatalf("Query error = %v, want denied compound member FirstName", err)
	}
	if repo.findCalled {
		t.Fatal("repo.Find was called after compound target member FLS denial")
	}
}

func TestQueryParentFilterBuildsPlansForStructuredClauses(t *testing.T) {
	for _, tc := range []struct {
		name   string
		mutate func(*queryspec.QuerySpec)
	}{
		{
			name: "criteria",
			mutate: func(req *queryspec.QuerySpec) {
				req.Filter = queryspec.CriteriaPredicate([]queryspec.QueryCriterion{{
					Field: "Account.Industry",
					Op:    "=",
					Val:   "Technology",
				}})
			},
		},
		{
			name: "sort field",
			mutate: func(req *queryspec.QuerySpec) {
				req.SortField = "Account.Industry"
			},
		},
		{
			name: "order by",
			mutate: func(req *queryspec.QuerySpec) {
				req.OrderBy = []queryspec.SortCriterion{{Field: "Account.Industry", Direction: "ASC"}}
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := parentFilterQueryService(repo,
				parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}}),
				parentFilterAccountSchema(models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString}),
			)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
			}
			tc.mutate(&req)

			_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if err != nil {
				t.Fatalf("QueryWithSpec: %v", err)
			}
			if !repo.findCalled {
				t.Fatal("repo.Find was not called after structured parent clause validation")
			}
			assertParentFilterPlan(t, repo.findReq.ParentFilterPaths, *defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"))
		})
	}
}

func TestQueryRejectsUnsupportedParentFieldStructuredClausesBeforeRepository(t *testing.T) {
	for _, tc := range []struct {
		name   string
		mutate func(*queryspec.QuerySpec)
	}{
		{
			name: "criteria",
			mutate: func(req *queryspec.QuerySpec) {
				req.Filter = queryspec.CriteriaPredicate([]queryspec.QueryCriterion{{
					Field: "Contact.Industry",
					Op:    "=",
					Val:   "Technology",
				}})
			},
		},
		{
			name: "sort field",
			mutate: func(req *queryspec.QuerySpec) {
				req.SortField = "Contact.Industry"
			},
		},
		{
			name: "order by",
			mutate: func(req *queryspec.QuerySpec) {
				req.OrderBy = []queryspec.SortCriterion{{Field: "Contact.Industry", Direction: "ASC"}}
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := parentFilterQueryService(repo,
				parentFilterRootSchema(models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}}),
				parentFilterAccountSchema(models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString}),
			)
			req := queryspec.QuerySpec{
				ObjectAPIName: "Opportunity",
				SelectFields:  []string{constants.FieldID},
			}
			tc.mutate(&req)

			_, err := qs.QueryWithSpec(context.Background(), req, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if err == nil || !strings.Contains(err.Error(), "relationship field \"Contact\" is not declared") {
				t.Fatalf("Query error = %v, want structured parent clause rejected", err)
			}
			if repo.findCalled {
				t.Fatal("repo.Find was called after structured parent clause rejection")
			}
		})
	}
}

type parentFilterPlanAssertion struct {
	rawPath                                           string
	hops                                              []parentFilterHopAssertion
	relationship                                      string
	rootFK                                            string
	rootFKPhysical                                    string
	targetObject                                      string
	targetTable                                       string
	targetID                                          string
	targetField                                       string
	targetPhysical                                    string
	targetCompoundParts                               []string
	targetVisibilityDenyAll                           bool
	targetVisibilityAllowedIDPhysical                 string
	targetVisibilityAllowedIDs                        []string
	targetVisibilityOwnerPhysical                     string
	targetVisibilityOwnerIDs                          []string
	targetVisibilityGroupOwnerIDs                     []string
	targetVisibilityManualShareUserID                 string
	targetVisibilityTeamMemberUserID                  string
	targetVisibilitySharingRuleOwnerIDs               []string
	targetVisibilitySharingRuleFromGroupIDs           []string
	targetVisibilityOwnerVisibilityOwnerPhysical      string
	targetVisibilityOwnerVisibilityVisibilityPhysical string
	targetVisibilityOwnerVisibilityUserID             string
	targetVisibilityOwnerVisibilityOwnerValues        []string
	targetVisibilityOwnerVisibilityPublicValues       []string
	targetVisibilityCriteriaExpressions               []string
	targetVisibilityCriteriaFields                    []models.QueryParentFilterCriteriaField
	targetVisibilityTargetRecordFilters               []models.QueryParentFilterTargetRecordVisibility
	targetIsDeleted                                   string
}

type parentFilterHopAssertion struct {
	relationship                            string
	sourceObject                            string
	foreignKey                              string
	foreignPhysical                         string
	targetObject                            string
	targetTable                             string
	targetID                                string
	targetIsDeleted                         string
	targetDenyAll                           bool
	targetAllowedIDPhysical                 string
	targetAllowedIDs                        []string
	targetOwnerVisibilityOwnerPhysical      string
	targetOwnerVisibilityVisibilityPhysical string
	targetOwnerVisibilityUserID             string
	targetOwnerVisibilityOwnerValues        []string
	targetOwnerVisibilityPublicValues       []string
	targetTargetRecordFilters               []models.QueryParentFilterTargetRecordVisibility
}

func defaultParentFilterPlan(rawPath string, relationship string, rootFK string, targetObject string, targetField string) *parentFilterPlanAssertion {
	return &parentFilterPlanAssertion{
		rawPath:        rawPath,
		relationship:   relationship,
		rootFK:         rootFK,
		rootFKPhysical: rootFK,
		targetObject:   targetObject,
		targetTable:    targetObject,
		targetID:       constants.FieldID,
		targetField:    targetField,
		targetPhysical: targetField,
	}
}

func multiHopOwnerNamePlan() *parentFilterPlanAssertion {
	return &parentFilterPlanAssertion{
		rawPath:        "Account.Owner.Name",
		relationship:   "Account",
		rootFK:         "AccountId",
		rootFKPhysical: "AccountId",
		targetObject:   "User",
		targetTable:    "User",
		targetID:       constants.FieldID,
		targetField:    "Name",
		targetPhysical: "Name",
		hops: []parentFilterHopAssertion{
			{
				relationship:    "Account",
				sourceObject:    "Opportunity",
				foreignKey:      "AccountId",
				foreignPhysical: "AccountId",
				targetObject:    "Account",
				targetTable:     "Account",
				targetID:        constants.FieldID,
			},
			{
				relationship:    "Owner",
				sourceObject:    "Account",
				foreignKey:      "OwnerId",
				foreignPhysical: "OwnerId",
				targetObject:    "User",
				targetTable:     "User",
				targetID:        constants.FieldID,
			},
		},
	}
}

func assertParentFilterPlan(t *testing.T, paths []models.QueryParentFilterPath, want parentFilterPlanAssertion) {
	t.Helper()
	if len(paths) != 1 {
		t.Fatalf("ParentFilterPaths len = %d, want 1: %#v", len(paths), paths)
	}
	assertParentFilterPath(t, paths[0], want)
}

func assertParentFilterPlans(t *testing.T, paths []models.QueryParentFilterPath, wants ...parentFilterPlanAssertion) {
	t.Helper()
	if len(paths) != len(wants) {
		t.Fatalf("ParentFilterPaths len = %d, want %d: %#v", len(paths), len(wants), paths)
	}
	for i := range wants {
		assertParentFilterPath(t, paths[i], wants[i])
	}
}

func assertParentFilterPath(t *testing.T, got models.QueryParentFilterPath, want parentFilterPlanAssertion) {
	t.Helper()
	if got.RawPath != want.rawPath {
		t.Fatalf("RawPath = %q, want %q", got.RawPath, want.rawPath)
	}
	if len(got.RelationshipHops) != len(want.hops) {
		t.Fatalf("RelationshipHops len = %d, want %d: %#v", len(got.RelationshipHops), len(want.hops), got.RelationshipHops)
	}
	for i := range want.hops {
		assertParentFilterHop(t, got.RelationshipHops[i], want.hops[i])
	}
	if got.RelationshipName != want.relationship {
		t.Fatalf("RelationshipName = %q, want %q", got.RelationshipName, want.relationship)
	}
	if got.RootForeignKeyField != want.rootFK {
		t.Fatalf("RootForeignKeyField = %q, want %q", got.RootForeignKeyField, want.rootFK)
	}
	if got.RootForeignKeyPhysicalName != want.rootFKPhysical {
		t.Fatalf("RootForeignKeyPhysicalName = %q, want %q", got.RootForeignKeyPhysicalName, want.rootFKPhysical)
	}
	if got.TargetObjectAPIName != want.targetObject {
		t.Fatalf("TargetObjectAPIName = %q, want %q", got.TargetObjectAPIName, want.targetObject)
	}
	if got.TargetTable != want.targetTable {
		t.Fatalf("TargetTable = %q, want %q", got.TargetTable, want.targetTable)
	}
	if got.TargetIDPhysicalName != want.targetID {
		t.Fatalf("TargetIDPhysicalName = %q, want %q", got.TargetIDPhysicalName, want.targetID)
	}
	if got.TargetFieldAPIName != want.targetField {
		t.Fatalf("TargetFieldAPIName = %q, want %q", got.TargetFieldAPIName, want.targetField)
	}
	if got.TargetFieldPhysicalName != want.targetPhysical {
		t.Fatalf("TargetFieldPhysicalName = %q, want %q", got.TargetFieldPhysicalName, want.targetPhysical)
	}
	if strings.Join(got.TargetFieldCompoundMemberPhysicalNames, ",") != strings.Join(want.targetCompoundParts, ",") {
		t.Fatalf("TargetFieldCompoundMemberPhysicalNames = %#v, want %#v", got.TargetFieldCompoundMemberPhysicalNames, want.targetCompoundParts)
	}
	if got.TargetVisibilityDenyAll != want.targetVisibilityDenyAll {
		t.Fatalf("TargetVisibilityDenyAll = %v, want %v", got.TargetVisibilityDenyAll, want.targetVisibilityDenyAll)
	}
	if got.TargetVisibilityAllowedIDPhysicalName != want.targetVisibilityAllowedIDPhysical {
		t.Fatalf("TargetVisibilityAllowedIDPhysicalName = %q, want %q", got.TargetVisibilityAllowedIDPhysicalName, want.targetVisibilityAllowedIDPhysical)
	}
	if strings.Join(got.TargetVisibilityAllowedIDs, ",") != strings.Join(want.targetVisibilityAllowedIDs, ",") {
		t.Fatalf("TargetVisibilityAllowedIDs = %#v, want %#v", got.TargetVisibilityAllowedIDs, want.targetVisibilityAllowedIDs)
	}
	if got.TargetVisibilityOwnerPhysicalName != want.targetVisibilityOwnerPhysical {
		t.Fatalf("TargetVisibilityOwnerPhysicalName = %q, want %q", got.TargetVisibilityOwnerPhysicalName, want.targetVisibilityOwnerPhysical)
	}
	if strings.Join(got.TargetVisibilityOwnerUserIDs, ",") != strings.Join(want.targetVisibilityOwnerIDs, ",") {
		t.Fatalf("TargetVisibilityOwnerUserIDs = %#v, want %#v", got.TargetVisibilityOwnerUserIDs, want.targetVisibilityOwnerIDs)
	}
	if strings.Join(got.TargetVisibilityGroupOwnerIDs, ",") != strings.Join(want.targetVisibilityGroupOwnerIDs, ",") {
		t.Fatalf("TargetVisibilityGroupOwnerIDs = %#v, want %#v", got.TargetVisibilityGroupOwnerIDs, want.targetVisibilityGroupOwnerIDs)
	}
	if got.TargetVisibilityManualShareUserID != want.targetVisibilityManualShareUserID {
		t.Fatalf("TargetVisibilityManualShareUserID = %q, want %q", got.TargetVisibilityManualShareUserID, want.targetVisibilityManualShareUserID)
	}
	if got.TargetVisibilityTeamMemberUserID != want.targetVisibilityTeamMemberUserID {
		t.Fatalf("TargetVisibilityTeamMemberUserID = %q, want %q", got.TargetVisibilityTeamMemberUserID, want.targetVisibilityTeamMemberUserID)
	}
	if strings.Join(got.TargetVisibilitySharingRuleOwnerUserIDs, ",") != strings.Join(want.targetVisibilitySharingRuleOwnerIDs, ",") {
		t.Fatalf("TargetVisibilitySharingRuleOwnerUserIDs = %#v, want %#v", got.TargetVisibilitySharingRuleOwnerUserIDs, want.targetVisibilitySharingRuleOwnerIDs)
	}
	if strings.Join(got.TargetVisibilitySharingRuleFromGroupIDs, ",") != strings.Join(want.targetVisibilitySharingRuleFromGroupIDs, ",") {
		t.Fatalf("TargetVisibilitySharingRuleFromGroupIDs = %#v, want %#v", got.TargetVisibilitySharingRuleFromGroupIDs, want.targetVisibilitySharingRuleFromGroupIDs)
	}
	if got.TargetVisibilityOwnerVisibilityOwnerPhysicalName != want.targetVisibilityOwnerVisibilityOwnerPhysical {
		t.Fatalf("TargetVisibilityOwnerVisibilityOwnerPhysicalName = %q, want %q", got.TargetVisibilityOwnerVisibilityOwnerPhysicalName, want.targetVisibilityOwnerVisibilityOwnerPhysical)
	}
	if got.TargetVisibilityOwnerVisibilityVisibilityPhysicalName != want.targetVisibilityOwnerVisibilityVisibilityPhysical {
		t.Fatalf("TargetVisibilityOwnerVisibilityVisibilityPhysicalName = %q, want %q", got.TargetVisibilityOwnerVisibilityVisibilityPhysicalName, want.targetVisibilityOwnerVisibilityVisibilityPhysical)
	}
	if got.TargetVisibilityOwnerVisibilityUserID != want.targetVisibilityOwnerVisibilityUserID {
		t.Fatalf("TargetVisibilityOwnerVisibilityUserID = %q, want %q", got.TargetVisibilityOwnerVisibilityUserID, want.targetVisibilityOwnerVisibilityUserID)
	}
	if strings.Join(got.TargetVisibilityOwnerVisibilityOwnerValues, ",") != strings.Join(want.targetVisibilityOwnerVisibilityOwnerValues, ",") {
		t.Fatalf("TargetVisibilityOwnerVisibilityOwnerValues = %#v, want %#v", got.TargetVisibilityOwnerVisibilityOwnerValues, want.targetVisibilityOwnerVisibilityOwnerValues)
	}
	if strings.Join(got.TargetVisibilityOwnerVisibilityPublicValues, ",") != strings.Join(want.targetVisibilityOwnerVisibilityPublicValues, ",") {
		t.Fatalf("TargetVisibilityOwnerVisibilityPublicValues = %#v, want %#v", got.TargetVisibilityOwnerVisibilityPublicValues, want.targetVisibilityOwnerVisibilityPublicValues)
	}
	if strings.Join(got.TargetVisibilityCriteriaExpressions, ",") != strings.Join(want.targetVisibilityCriteriaExpressions, ",") {
		t.Fatalf("TargetVisibilityCriteriaExpressions = %#v, want %#v", got.TargetVisibilityCriteriaExpressions, want.targetVisibilityCriteriaExpressions)
	}
	if len(got.TargetVisibilityCriteriaFields) != len(want.targetVisibilityCriteriaFields) {
		t.Fatalf("TargetVisibilityCriteriaFields len = %d, want %d: %#v", len(got.TargetVisibilityCriteriaFields), len(want.targetVisibilityCriteriaFields), got.TargetVisibilityCriteriaFields)
	}
	for i := range want.targetVisibilityCriteriaFields {
		if got.TargetVisibilityCriteriaFields[i] != want.targetVisibilityCriteriaFields[i] {
			t.Fatalf("TargetVisibilityCriteriaFields[%d] = %#v, want %#v", i, got.TargetVisibilityCriteriaFields[i], want.targetVisibilityCriteriaFields[i])
		}
	}
	assertParentFilterTargetRecordVisibility(t, got.TargetVisibilityTargetRecordFilters, want.targetVisibilityTargetRecordFilters)
	if got.TargetHasIsDeleted != (want.targetIsDeleted != "") {
		t.Fatalf("TargetHasIsDeleted = %v, want %v", got.TargetHasIsDeleted, want.targetIsDeleted != "")
	}
	if got.TargetIsDeletedPhysicalName != want.targetIsDeleted {
		t.Fatalf("TargetIsDeletedPhysicalName = %q, want %q", got.TargetIsDeletedPhysicalName, want.targetIsDeleted)
	}
	if got.TargetVisibilityDenyAll != want.targetVisibilityDenyAll {
		t.Fatalf("TargetVisibilityDenyAll = %v, want %v", got.TargetVisibilityDenyAll, want.targetVisibilityDenyAll)
	}
}

func assertParentFilterHop(t *testing.T, got models.QueryParentFilterHop, want parentFilterHopAssertion) {
	t.Helper()
	if got.RelationshipName != want.relationship {
		t.Fatalf("RelationshipName = %q, want %q", got.RelationshipName, want.relationship)
	}
	if got.SourceObjectAPIName != want.sourceObject {
		t.Fatalf("SourceObjectAPIName = %q, want %q", got.SourceObjectAPIName, want.sourceObject)
	}
	if got.ForeignKeyField != want.foreignKey {
		t.Fatalf("ForeignKeyField = %q, want %q", got.ForeignKeyField, want.foreignKey)
	}
	if got.ForeignKeyPhysicalName != want.foreignPhysical {
		t.Fatalf("ForeignKeyPhysicalName = %q, want %q", got.ForeignKeyPhysicalName, want.foreignPhysical)
	}
	if got.TargetObjectAPIName != want.targetObject {
		t.Fatalf("TargetObjectAPIName = %q, want %q", got.TargetObjectAPIName, want.targetObject)
	}
	if got.TargetTable != want.targetTable {
		t.Fatalf("TargetTable = %q, want %q", got.TargetTable, want.targetTable)
	}
	if got.TargetIDPhysicalName != want.targetID {
		t.Fatalf("TargetIDPhysicalName = %q, want %q", got.TargetIDPhysicalName, want.targetID)
	}
	if got.TargetHasIsDeleted != (want.targetIsDeleted != "") {
		t.Fatalf("TargetHasIsDeleted = %v, want %v", got.TargetHasIsDeleted, want.targetIsDeleted != "")
	}
	if got.TargetIsDeletedPhysicalName != want.targetIsDeleted {
		t.Fatalf("TargetIsDeletedPhysicalName = %q, want %q", got.TargetIsDeletedPhysicalName, want.targetIsDeleted)
	}
	if got.TargetVisibilityDenyAll != want.targetDenyAll {
		t.Fatalf("TargetVisibilityDenyAll = %v, want %v", got.TargetVisibilityDenyAll, want.targetDenyAll)
	}
	if got.TargetVisibilityAllowedIDPhysicalName != want.targetAllowedIDPhysical {
		t.Fatalf("TargetVisibilityAllowedIDPhysicalName = %q, want %q", got.TargetVisibilityAllowedIDPhysicalName, want.targetAllowedIDPhysical)
	}
	if strings.Join(got.TargetVisibilityAllowedIDs, ",") != strings.Join(want.targetAllowedIDs, ",") {
		t.Fatalf("TargetVisibilityAllowedIDs = %#v, want %#v", got.TargetVisibilityAllowedIDs, want.targetAllowedIDs)
	}
	if got.TargetVisibilityOwnerVisibilityOwnerPhysicalName != want.targetOwnerVisibilityOwnerPhysical {
		t.Fatalf("TargetVisibilityOwnerVisibilityOwnerPhysicalName = %q, want %q", got.TargetVisibilityOwnerVisibilityOwnerPhysicalName, want.targetOwnerVisibilityOwnerPhysical)
	}
	if got.TargetVisibilityOwnerVisibilityVisibilityPhysicalName != want.targetOwnerVisibilityVisibilityPhysical {
		t.Fatalf("TargetVisibilityOwnerVisibilityVisibilityPhysicalName = %q, want %q", got.TargetVisibilityOwnerVisibilityVisibilityPhysicalName, want.targetOwnerVisibilityVisibilityPhysical)
	}
	if got.TargetVisibilityOwnerVisibilityUserID != want.targetOwnerVisibilityUserID {
		t.Fatalf("TargetVisibilityOwnerVisibilityUserID = %q, want %q", got.TargetVisibilityOwnerVisibilityUserID, want.targetOwnerVisibilityUserID)
	}
	if strings.Join(got.TargetVisibilityOwnerVisibilityOwnerValues, ",") != strings.Join(want.targetOwnerVisibilityOwnerValues, ",") {
		t.Fatalf("TargetVisibilityOwnerVisibilityOwnerValues = %#v, want %#v", got.TargetVisibilityOwnerVisibilityOwnerValues, want.targetOwnerVisibilityOwnerValues)
	}
	if strings.Join(got.TargetVisibilityOwnerVisibilityPublicValues, ",") != strings.Join(want.targetOwnerVisibilityPublicValues, ",") {
		t.Fatalf("TargetVisibilityOwnerVisibilityPublicValues = %#v, want %#v", got.TargetVisibilityOwnerVisibilityPublicValues, want.targetOwnerVisibilityPublicValues)
	}
	assertParentFilterTargetRecordVisibility(t, got.TargetVisibilityTargetRecordFilters, want.targetTargetRecordFilters)
}

func assertParentFilterTargetRecordVisibility(t *testing.T, got []models.QueryParentFilterTargetRecordVisibility, want []models.QueryParentFilterTargetRecordVisibility) {
	t.Helper()
	if len(got) != len(want) {
		t.Fatalf("TargetVisibilityTargetRecordFilters len = %d, want %d: %#v", len(got), len(want), got)
	}
	for i := range want {
		if !reflect.DeepEqual(got[i], want[i]) {
			t.Fatalf("TargetVisibilityTargetRecordFilters[%d] = %#v, want %#v", i, got[i], want[i])
		}
	}
}

func TestRunAnalyticsRejectsFLSDeniedFilterAndHavingFields(t *testing.T) {
	for _, tc := range []struct {
		name  string
		query models.AnalyticsQuery
	}{
		{
			name: "filter expr",
			query: models.AnalyticsQuery{
				ObjectAPIName: constants.TableProcessInstanceHistory,
				Operation:     constants.AnalyticsOperationCount,
				FilterExpr:    "Comments != nil",
			},
		},
		{
			name: "having",
			query: models.AnalyticsQuery{
				ObjectAPIName: constants.TableProcessInstanceHistory,
				Operation:     constants.AnalyticsOperationCount,
				Having:        "COUNT(Comments) > 0",
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			repo := &clauseFLSRepository{}
			qs := clauseFLSQueryService(repo)

			_, err := qs.RunAnalytics(context.Background(), tc.query, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
			if err == nil || !strings.Contains(err.Error(), "Comments") {
				t.Fatalf("RunAnalytics error = %v, want denied Comments field", err)
			}
			if repo.analyticsCalled {
				t.Fatal("repo.RunAnalytics was called after a denied read clause")
			}
		})
	}
}

func TestRunAnalyticsBuildsParentFilterPlansForAggregateClauses(t *testing.T) {
	repo := &clauseFLSRepository{}
	qs := parentFilterQueryService(repo,
		parentFilterRootSchema(
			models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		),
		parentFilterAccountSchema(
			models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
		),
	)
	groupBy := "Account.Industry"
	field := constants.FieldID

	_, err := qs.RunAnalytics(context.Background(), models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationGroupBy,
		Field:         &field,
		AggregateType: "COUNT",
		FilterExpr:    "Account.Industry != nil",
		GroupBy:       &groupBy,
		ParentFilterPaths: []models.QueryParentFilterPath{{
			RawPath:                 "Account.Industry",
			RelationshipName:        "Account",
			RootForeignKeyField:     "AccountId",
			TargetObjectAPIName:     "WrongObject",
			TargetTable:             "WrongTable",
			TargetIDPhysicalName:    constants.FieldID,
			TargetFieldAPIName:      "Industry",
			TargetFieldPhysicalName: "wrong_column",
		}},
	}, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("RunAnalytics error = %v, want accepted aggregate parent clauses", err)
	}
	if !repo.analyticsCalled {
		t.Fatal("repo.RunAnalytics was not called for accepted aggregate parent clauses")
	}
	if repo.analyticsQuery.GroupBy == nil || *repo.analyticsQuery.GroupBy != groupBy {
		t.Fatalf("analytics groupBy = %#v, want %q", repo.analyticsQuery.GroupBy, groupBy)
	}
	assertParentFilterPlan(t, repo.analyticsQuery.ParentFilterPaths, *defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"))
}

func TestRunAnalyticsBuildsParentFilterPlansForAggregateHaving(t *testing.T) {
	repo := &clauseFLSRepository{}
	qs := parentFilterQueryService(repo,
		parentFilterRootSchema(
			models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		),
		parentFilterAccountSchema(
			models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
		),
	)
	groupBy := "StageName"

	_, err := qs.RunAnalytics(context.Background(), models.AnalyticsQuery{
		ObjectAPIName: "Opportunity",
		Operation:     constants.AnalyticsOperationGroupBy,
		GroupBy:       &groupBy,
		Having:        "Account.Industry != nil",
	}, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("RunAnalytics error = %v, want accepted aggregate parent having", err)
	}
	if !repo.analyticsCalled {
		t.Fatal("repo.RunAnalytics was not called for accepted aggregate parent having")
	}
	assertParentFilterPlan(t, repo.analyticsQuery.ParentFilterPaths, *defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"))
}

func TestQueryAggregateParentGroupByReturnsGroupPathKey(t *testing.T) {
	repo := &clauseFLSRepository{
		analyticsResult: []models.SObject{{
			"name": "Technology",
			"val":  int64(2),
		}},
	}
	qs := parentFilterQueryService(repo,
		parentFilterRootSchema(
			models.FieldMetadata{APIName: "AccountId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Account"}},
		),
		parentFilterAccountSchema(
			models.FieldMetadata{APIName: "Industry", Type: constants.FieldTypeString},
		),
	)

	result, err := qs.QueryWithSpec(context.Background(), queryspec.QuerySpec{
		ObjectAPIName: "Opportunity",
		SelectFields:  []string{"Account.Industry", "COUNT(Id) total"},
		Filter:        queryspec.FormulaPredicate("Account.Industry != nil"),
		GroupBy:       "Account.Industry",
	}, &models.UserSession{ID: "005-reader", ProfileID: "00e-standard"})
	if err != nil {
		t.Fatalf("QueryWithSpec error = %v, want accepted aggregate parent groupBy", err)
	}
	if !result.IsAggregate {
		t.Fatal("IsAggregate = false, want true")
	}
	if result.TotalSize != 1 || len(result.Records) != 1 {
		t.Fatalf("result size = totalSize %d len %d, want 1/1", result.TotalSize, len(result.Records))
	}
	if result.Records[0]["Account.Industry"] != "Technology" || result.Records[0]["total"] != int64(2) {
		t.Fatalf("aggregate row = %#v, want parent group key and alias", result.Records[0])
	}
	assertParentFilterPlan(t, repo.analyticsQuery.ParentFilterPaths, *defaultParentFilterPlan("Account.Industry", "Account", "AccountId", "Account", "Industry"))
}

func TestSystemContextBypassRejectsReadProtectedClauseField(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:      "Issue",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "SecretText", Type: constants.FieldTypeString, IsSystem: true, ReadProtectionPolicy: constants.FieldReadProtectionBlocked},
		},
	}
	metadataSvc := metadata.NewMetadataService(&searchMetadataRepository{schema: schema}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{}, metadataSvc, nil)
	qs := NewQueryService(&clauseFLSRepository{}, metadataSvc, permissionSvc, nil)
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "test metadata read block")

	readable, err := qs.fieldReadableForQuery(ctx, "Issue", schema.Fields[1], &models.UserSession{ID: "005-reader"})
	if err != nil {
		t.Fatalf("fieldReadableForQuery: %v", err)
	}
	if readable {
		t.Fatal("fieldReadableForQuery = true for read-protected field under SystemContext bypass; want false")
	}
	systemFields, err := qs.systemFieldsForQuery(ctx, "Issue", schema, &models.UserSession{ID: "005-reader"})
	if err != nil {
		t.Fatalf("systemFieldsForQuery: %v", err)
	}
	for _, apiName := range systemFields {
		if apiName == "SecretText" {
			t.Fatal("systemFieldsForQuery included read-protected system field; want filtered")
		}
	}
	err = qs.validateClauseFieldReadable(ctx, "Issue", schema, "SecretText", "WHERE", &models.UserSession{ID: "005-reader"})
	if err == nil || !strings.Contains(err.Error(), "SecretText") {
		t.Fatalf("validateClauseFieldReadable error = %v, want denied SecretText field", err)
	}
}

func TestClauseKeywordClassifierDoesNotKnowSemiJoinPlaceholderToken(t *testing.T) {
	if isClauseKeyword("__nx_semijoin_0__") {
		t.Fatal("isClauseKeyword recognized the L1 semi-join placeholder token; want app-blind classifier")
	}
}

func clauseFLSQueryService(repo *clauseFLSRepository) *QueryService {
	schema := &models.ObjectMetadata{
		APIName:      constants.TableProcessInstanceHistory,
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Comments", Type: constants.FieldTypeTextarea},
		},
	}
	metadataSvc := metadata.NewMetadataService(&searchMetadataRepository{schema: schema}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{
		fieldReads: map[string]bool{
			strings.ToLower(constants.TableProcessInstanceHistory + ".Comments"): false,
		},
	}, metadataSvc, nil)
	return NewQueryService(repo, metadataSvc, permissionSvc, nil)
}

func parentFilterQueryService(repo *clauseFLSRepository, root *models.ObjectMetadata, targetSchemas ...*models.ObjectMetadata) *QueryService {
	return parentFilterQueryServiceWithRelationships(repo, root, nil, targetSchemas...)
}

func parentFilterQueryServiceWithRelationships(repo *clauseFLSRepository, root *models.ObjectMetadata, relationships []*models.Relationship, targetSchemas ...*models.ObjectMetadata) *QueryService {
	return parentFilterQueryServiceWithAccess(repo, root, relationships, nil, nil, targetSchemas...)
}

func parentFilterQueryServiceWithAccess(repo *clauseFLSRepository, root *models.ObjectMetadata, relationships []*models.Relationship, objectReads map[string]bool, fieldReads map[string]bool, targetSchemas ...*models.ObjectMetadata) *QueryService {
	schemas := append([]*models.ObjectMetadata{root}, targetSchemas...)
	metadataSvc := metadata.NewMetadataService(&clauseFLSMetadataRepository{schemas: schemas, relationships: relationships}, nil, nil)
	permissionSvc := permissions.NewPermissionService(&searchPermissionRepository{objectReads: objectReads, fieldReads: fieldReads}, metadataSvc, nil)
	return NewQueryService(repo, metadataSvc, permissionSvc, nil)
}

type parentFilterRLSOverridePermissionChecker struct {
	*permissions.PermissionService
	results map[string]*domainrls.Result
}

func (p *parentFilterRLSOverridePermissionChecker) BuildRLSResult(ctx context.Context, schema *models.ObjectMetadata, objectName string, user *models.UserSession) *domainrls.Result {
	if p != nil && p.results != nil {
		if result := p.results[strings.ToLower(objectName)]; result != nil {
			return result
		}
	}
	return p.PermissionService.BuildRLSResult(ctx, schema, objectName, user)
}

func parentFilterRootSchema(fields ...models.FieldMetadata) *models.ObjectMetadata {
	schemaFields := []models.FieldMetadata{
		{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
	}
	schemaFields = append(schemaFields, fields...)
	return &models.ObjectMetadata{
		APIName:      "Opportunity",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields:       schemaFields,
	}
}

func parentFilterAccountSchema(fields ...models.FieldMetadata) *models.ObjectMetadata {
	return parentFilterObjectSchema("Account", fields...)
}

func parentFilterObjectSchema(apiName string, fields ...models.FieldMetadata) *models.ObjectMetadata {
	schemaFields := []models.FieldMetadata{
		{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
	}
	schemaFields = append(schemaFields, fields...)
	return &models.ObjectMetadata{
		APIName:      apiName,
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields:       schemaFields,
	}
}

type clauseFLSMetadataRepository struct {
	ports.MetadataRepository

	schemas       []*models.ObjectMetadata
	relationships []*models.Relationship
	sharingRules  map[string][]*models.SharingRule
}

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

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

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

func (r *clauseFLSMetadataRepository) GetSharingRules(ctx context.Context, objectAPIName string) ([]*models.SharingRule, error) {
	if r.sharingRules == nil {
		return nil, nil
	}
	return append([]*models.SharingRule(nil), r.sharingRules[strings.ToLower(objectAPIName)]...), nil
}

type clauseFLSRepository struct {
	ports.QueryRepository

	findCalled      bool
	findReq         models.QueryRequest
	analyticsCalled bool
	analyticsQuery  models.AnalyticsQuery
	analyticsResult interface{}
}

func (r *clauseFLSRepository) Find(ctx context.Context, schema *models.ObjectMetadata, req models.QueryRequest, rlsResult *domainrls.Result, visibleFields []string) (*models.QueryResult, error) {
	r.findCalled = true
	r.findReq = req
	return &models.QueryResult{Done: true}, nil
}

func (r *clauseFLSRepository) RunAnalytics(ctx context.Context, tableSchema *models.ObjectMetadata, q models.AnalyticsQuery, rlsResult *domainrls.Result) (interface{}, error) {
	r.analyticsCalled = true
	r.analyticsQuery = q
	if r.analyticsResult != nil {
		return r.analyticsResult, nil
	}
	return int64(0), nil
}
