package records

import (
	"context"
	"strings"
	"testing"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestIsValidatorSkippedFieldIncludesRecordTypeID(t *testing.T) {
	if !isValidatorSkippedField("RecordTypeId") {
		t.Fatal("RecordTypeId should be skipped because validateRecordTypeId is its dedicated owner")
	}
	if !isValidatorSkippedField("recordtypeid") {
		t.Fatal("RecordTypeId skip should be case-insensitive")
	}
	if !isValidatorSkippedField("OwnerId") {
		t.Fatal("OwnerId should remain skipped")
	}
	if isValidatorSkippedField("QuoteId") {
		t.Fatal("ordinary non-polymorphic references should not be skipped")
	}
}

func TestIsNonPolymorphicReferenceIncludesSingleTargetPolymorphic(t *testing.T) {
	field := &models.FieldMetadata{
		APIName:       "UserOrGroupId",
		Type:          constants.FieldTypeReference,
		ReferenceTo:   []string{"User"},
		IsPolymorphic: true,
	}
	if !isNonPolymorphicReference(field) {
		t.Fatal("single-target transitional polymorphic reference should still use the user-level lookup access gate")
	}
}

func TestIsNonPolymorphicReferenceIncludesMasterDetail(t *testing.T) {
	field := &models.FieldMetadata{
		APIName:     "Parent__c",
		Type:        constants.FieldTypeMasterDetail,
		ReferenceTo: []string{"Parent__c"},
	}
	if !isNonPolymorphicReference(field) {
		t.Fatal("master-detail fields should use the same lookup access gate as reference fields")
	}
}

func TestIsNonPolymorphicReferenceSkipsMultiTargetPolymorphic(t *testing.T) {
	field := &models.FieldMetadata{
		APIName:       "ActorId",
		Type:          constants.FieldTypeReference,
		ReferenceTo:   []string{"User", "Group"},
		IsPolymorphic: true,
	}
	if isNonPolymorphicReference(field) {
		t.Fatal("multi-target polymorphic reference should not use the single-target lookup access gate")
	}
}

func TestValidateNonPolymorphicLookupsLocksMasterDetailParentInsideCoordinator(t *testing.T) {
	parent := relationshipDeleteParentSchema()
	// A master-detail parent must be FOR UPDATE-locked before the validation
	// read. (A plain optional lookup is validated but NOT locked under the
	// narrowed lookup-lock predicate; only MD/sharing-parent/rollup-feeding
	// FKs take the parent lock.)
	child := relationshipDeleteChildSchema(models.FieldMetadata{
		APIName:     "ParentId__c",
		Type:        constants.FieldTypeMasterDetail,
		ReferenceTo: []string{"Parent__c"},
		Createable:  true,
		Updateable:  true,
	})
	repo := &relationshipDeleteRepo{
		records: map[string]models.SObject{
			"Parent__c": {
				constants.FieldID:        "parent-1",
				constants.FieldName:      "parent",
				constants.FieldIsDeleted: false,
			},
		},
	}
	ps := newRollupBoundaryTestService(repo, parent, child)
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "lookup parent lock test")

	err := ps.runGroupMembershipAwarePessimisticTransactionForObjects(ctx, []string{"Child__c"}, func(txCtx context.Context) error {
		return ps.validateNonPolymorphicLookups(txCtx, models.SObject{"ParentId__c": "parent-1"}, child, recordEntrypointUser())
	})
	if err != nil {
		t.Fatalf("validateNonPolymorphicLookups: %v", err)
	}

	parentLockAt := relationshipDeleteCallIndex(repo.calls, "lock:Parent__c:parent-1")
	parentFindAt := relationshipDeleteCallIndex(repo.calls, "find:Parent__c:parent-1")
	if parentLockAt < 0 || parentFindAt < 0 {
		t.Fatalf("missing parent lock/find calls: %#v", repo.calls)
	}
	if parentLockAt > parentFindAt {
		t.Fatalf("parent lookup read ran before parent lock: %#v", repo.calls)
	}
}

func TestValidateNonPolymorphicLookupsLocksCascadeReferenceParentInsideCoordinator(t *testing.T) {
	parent := relationshipDeleteParentSchema()
	cascade := constants.DeleteRuleCascade
	child := relationshipDeleteChildSchema(models.FieldMetadata{
		APIName:     "ParentId__c",
		Type:        constants.FieldTypeReference,
		ReferenceTo: []string{"Parent__c"},
		DeleteRule:  &cascade,
		Createable:  true,
		Updateable:  true,
	})
	repo := &relationshipDeleteRepo{
		records: map[string]models.SObject{
			"Parent__c": {
				constants.FieldID:        "parent-1",
				constants.FieldName:      "parent",
				constants.FieldIsDeleted: false,
			},
		},
	}
	ps := newRollupBoundaryTestService(repo, parent, child)
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "cascade lookup parent lock test")

	err := ps.runGroupMembershipAwarePessimisticTransactionForObjects(ctx, []string{"Child__c"}, func(txCtx context.Context) error {
		return ps.validateNonPolymorphicLookups(txCtx, models.SObject{"ParentId__c": "parent-1"}, child, recordEntrypointUser())
	})
	if err != nil {
		t.Fatalf("validateNonPolymorphicLookups: %v", err)
	}

	parentLockAt := relationshipDeleteCallIndex(repo.calls, "lock:Parent__c:parent-1")
	parentFindAt := relationshipDeleteCallIndex(repo.calls, "find:Parent__c:parent-1")
	if parentLockAt < 0 || parentFindAt < 0 {
		t.Fatalf("missing parent lock/find calls: %#v", repo.calls)
	}
	if parentLockAt > parentFindAt {
		t.Fatalf("parent lookup read ran before parent lock: %#v", repo.calls)
	}
}

func TestValidateNonPolymorphicLookupsLocksRelationshipCascadeParentInsideCoordinator(t *testing.T) {
	parent := relationshipDeleteParentSchema()
	child := relationshipDeleteChildSchema(models.FieldMetadata{
		APIName:     "ParentId__c",
		Type:        constants.FieldTypeReference,
		ReferenceTo: []string{"Parent__c"},
		Createable:  true,
		Updateable:  true,
	})
	repo := &relationshipDeleteRepo{
		records: map[string]models.SObject{
			"Parent__c": {
				constants.FieldID:        "parent-1",
				constants.FieldName:      "parent",
				constants.FieldIsDeleted: false,
			},
		},
	}
	ps := newRollupBoundaryTestServiceWithMetadataRepo(repo, &stubUpsertMetaRepo{
		schemas: []*models.ObjectMetadata{parent, child},
		relationships: []*models.Relationship{{
			ChildObjectAPIName:  "Child__c",
			ParentObjectAPIName: "Parent__c",
			FieldAPIName:        "ParentId__c",
			CascadeDelete:       true,
		}},
	})
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "relationship cascade lookup parent lock test")

	err := ps.runGroupMembershipAwarePessimisticTransactionForObjects(ctx, []string{"Child__c"}, func(txCtx context.Context) error {
		return ps.validateNonPolymorphicLookups(txCtx, models.SObject{"ParentId__c": "parent-1"}, child, recordEntrypointUser())
	})
	if err != nil {
		t.Fatalf("validateNonPolymorphicLookups: %v", err)
	}

	parentLockAt := relationshipDeleteCallIndex(repo.calls, "lock:Parent__c:parent-1")
	parentFindAt := relationshipDeleteCallIndex(repo.calls, "find:Parent__c:parent-1")
	if parentLockAt < 0 || parentFindAt < 0 {
		t.Fatalf("missing parent lock/find calls: %#v", repo.calls)
	}
	if parentLockAt > parentFindAt {
		t.Fatalf("parent lookup read ran before parent lock: %#v", repo.calls)
	}
}

func TestValidateNonPolymorphicLookupsValidatesPlainLookupWithoutLocking(t *testing.T) {
	parent := relationshipDeleteParentSchema()
	// Plain optional lookup: validated for existence/access but never locked,
	// so unrelated writes to records sharing this parent (e.g. a shared
	// low-cardinality Pricebook2) do not serialize on a FOR UPDATE.
	child := relationshipDeleteChildSchema(models.FieldMetadata{
		APIName:     "ParentId__c",
		Type:        constants.FieldTypeReference,
		ReferenceTo: []string{"Parent__c"},
		Createable:  true,
		Updateable:  true,
	})
	repo := &relationshipDeleteRepo{
		records: map[string]models.SObject{
			"Parent__c": {
				constants.FieldID:        "parent-1",
				constants.FieldName:      "parent",
				constants.FieldIsDeleted: false,
			},
		},
	}
	ps := newRollupBoundaryTestService(repo, parent, child)
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "plain lookup no-lock test")

	err := ps.runGroupMembershipAwarePessimisticTransactionForObjects(ctx, []string{"Child__c"}, func(txCtx context.Context) error {
		return ps.validateNonPolymorphicLookups(txCtx, models.SObject{"ParentId__c": "parent-1"}, child, recordEntrypointUser())
	})
	if err != nil {
		t.Fatalf("validateNonPolymorphicLookups: %v", err)
	}

	if parentLockAt := relationshipDeleteCallIndex(repo.calls, "lock:Parent__c:parent-1"); parentLockAt >= 0 {
		t.Fatalf("plain lookup parent was FOR UPDATE-locked; want validate-only: %#v", repo.calls)
	}
	if parentFindAt := relationshipDeleteCallIndex(repo.calls, "find:Parent__c:parent-1"); parentFindAt < 0 {
		t.Fatalf("plain lookup parent was not validated via a read: %#v", repo.calls)
	}
}

func TestValidateNonPolymorphicLookupsUsesAccessAwareOwnerForParentControlledLookup(t *testing.T) {
	ps, repo := newPCOpportunityLineItemReproService()

	schema := ps.metadata.GetSchema(t.Context(), "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}

	err := ps.validateNonPolymorphicLookups(t.Context(), models.SObject{
		"OpportunityId": "opp1",
	}, schema, &models.UserSession{ID: "u1", ProfileID: "p1"})
	if err != nil {
		t.Fatalf("validateNonPolymorphicLookups: %v", err)
	}
	if repo.findOneCalls == 0 {
		t.Fatal("parent-controlled lookup validation did not use the access-aware Retrieve/FindOne path")
	}
}

func TestValidateNonPolymorphicLookupsRevalidatesSuccessfulProofAcrossCalls(t *testing.T) {
	ps, repo := newPCOpportunityLineItemReproService()

	schema := ps.metadata.GetSchema(t.Context(), "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}

	user := &models.UserSession{ID: "u1", ProfileID: "p1"}
	payload := models.SObject{"OpportunityId": "opp1"}
	if err := ps.validateNonPolymorphicLookups(t.Context(), payload, schema, user); err != nil {
		t.Fatalf("first validateNonPolymorphicLookups: %v", err)
	}
	callsAfterFirst := repo.findOneCalls
	if callsAfterFirst == 0 {
		t.Fatal("first validation did not prove the target through the repository")
	}
	if err := ps.validateNonPolymorphicLookups(t.Context(), payload, schema, user); err != nil {
		t.Fatalf("second validateNonPolymorphicLookups: %v", err)
	}
	if repo.findOneCalls <= callsAfterFirst {
		t.Fatalf("second validation did not re-read target: before=%d after=%d", callsAfterFirst, repo.findOneCalls)
	}
}

func TestValidateNonPolymorphicLookupsParentControlledLookupRequiresAccessibleTarget(t *testing.T) {
	ps, repo := newPCOpportunityLineItemReproService()
	repo.findOneResult = nil
	repo.existingRecords["Opportunity:opp1"] = true

	schema := ps.metadata.GetSchema(t.Context(), "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}

	err := ps.validateNonPolymorphicLookups(t.Context(), models.SObject{
		"OpportunityId": "opp1",
	}, schema, &models.UserSession{ID: "u1", ProfileID: "p1"})
	if err == nil {
		t.Fatal("validateNonPolymorphicLookups returned nil, want validation error")
	}
	if !appErrors.IsValidation(err) {
		t.Fatalf("validateNonPolymorphicLookups error = %T %v, want validation error", err, err)
	}
	if !strings.Contains(err.Error(), `OpportunityId: referenced Opportunity record "opp1" is not accessible or does not exist`) {
		t.Fatalf("validateNonPolymorphicLookups error = %q, want parent lookup validation message", err.Error())
	}
	if repo.findOneCalls == 0 {
		t.Fatal("parent-controlled lookup validation bypassed the access-aware Retrieve/FindOne path")
	}
}

func TestValidateNonPolymorphicLookupsRequiredSharingParentRequiresEdit(t *testing.T) {
	ps, _ := newPCOpportunityLineItemReproService()
	ps.permissions = permissions.NewPermissionService(&parentOnlyPermRepo{
		parentObject: "Opportunity",
		parentPerm: &models.ObjectPermissions{
			SobjectType:     "Opportunity",
			PermissionsRead: true,
			// No PermissionsEdit: required/master-detail sharing-parent FK
			// must reject even though the parent is readable.
		},
	}, ps.metadata, nil)

	schema := ps.metadata.GetSchema(t.Context(), "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}

	err := ps.validateNonPolymorphicLookups(t.Context(), models.SObject{
		"OpportunityId": "opp1",
	}, schema, &models.UserSession{ID: "u1", ProfileID: "p1"})
	if err == nil {
		t.Fatal("validateNonPolymorphicLookups returned nil, want validation error when required sharing parent is not editable")
	}
	if !appErrors.IsValidation(err) {
		t.Fatalf("validateNonPolymorphicLookups error = %T %v, want validation error", err, err)
	}
}

func TestValidateNonPolymorphicLookupsOptionalSharingParentWriteRequiresEdit(t *testing.T) {
	ps, _ := newPCOpportunityLineItemReproService()
	ps.permissions = permissions.NewPermissionService(&parentOnlyPermRepo{
		parentObject: "Opportunity",
		parentPerm: &models.ObjectPermissions{
			SobjectType:               "Opportunity",
			PermissionsRead:           true,
			PermissionsViewAllRecords: true,
		},
	}, ps.metadata, nil)

	schema := ps.metadata.GetSchema(t.Context(), "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}
	for i := range schema.Fields {
		if schema.Fields[i].APIName == "OpportunityId" {
			schema.Fields[i].Type = constants.FieldTypeReference
			schema.Fields[i].Required = false
			schema.Fields[i].IsMasterDetail = false
		}
	}

	err := ps.validateNonPolymorphicLookups(t.Context(), models.SObject{
		"OpportunityId": "opp1",
	}, schema, &models.UserSession{ID: "u1", ProfileID: "p1"})
	if err == nil {
		t.Fatal("validateNonPolymorphicLookups returned nil, want validation error when optional sharing-parent write is not editable")
	}
	if !appErrors.IsValidation(err) {
		t.Fatalf("validateNonPolymorphicLookups error = %T %v, want validation error", err, err)
	}
}

func TestValidateNonPolymorphicLookupsCreateIntentRequiresParentWhenParentEditNeedsRecordContext(t *testing.T) {
	ctx := t.Context()
	ps, _ := newPCOpportunityLineItemReproService()
	parentSchema := ps.metadata.GetSchema(ctx, "Opportunity")
	if parentSchema == nil {
		t.Fatal("Opportunity schema missing")
	}
	parentSchema.AccessPolicy = &models.ObjectAccessPolicy{
		Mode:       "derived",
		ObjectEdit: &models.AccessPolicyExpr{Kind: "recordFieldEqualsCurrentUser", Field: constants.FieldOwnerID},
	}
	schema := ps.metadata.GetSchema(ctx, "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}
	for i := range schema.Fields {
		if schema.Fields[i].APIName == "OpportunityId" {
			schema.Fields[i].Type = constants.FieldTypeReference
			schema.Fields[i].Required = false
			schema.Fields[i].IsMasterDetail = false
		}
	}

	err := ps.validateNonPolymorphicLookups(
		withNonPolymorphicLookupCreateIntent(ctx),
		models.SObject{},
		schema,
		&models.UserSession{ID: "u1", ProfileID: "p1"},
	)
	if err == nil {
		t.Fatal("create intent without sharing-parent FK returned nil, want validation error")
	}
	if !appErrors.IsValidation(err) {
		t.Fatalf("create intent error = %T %v, want validation error", err, err)
	}

	if err := ps.validateNonPolymorphicLookups(ctx, models.SObject{"Quantity": 1}, schema, &models.UserSession{ID: "u1", ProfileID: "p1"}); err != nil {
		t.Fatalf("non-create update without reparent should not require sharing-parent FK: %v", err)
	}
}

func TestValidateNonPolymorphicLookupsCreateIntentMalformedSharingParentFieldFailsClosed(t *testing.T) {
	ctx := t.Context()
	ps, _ := newPCOpportunityLineItemReproService()
	schema := ps.metadata.GetSchema(ctx, "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}
	schema.RequiredWritePermission = "EditTask"
	filtered := schema.Fields[:0]
	for _, field := range schema.Fields {
		if !strings.EqualFold(field.APIName, "OpportunityId") {
			filtered = append(filtered, field)
		}
	}
	schema.Fields = filtered

	err := ps.validateNonPolymorphicLookups(
		withNonPolymorphicLookupCreateIntent(ctx),
		models.SObject{},
		schema,
		&models.UserSession{ID: "u1", ProfileID: "p1"},
	)
	if err == nil {
		t.Fatal("create intent with malformed sharing-parent field returned nil, want validation error")
	}
	if !appErrors.IsValidation(err) {
		t.Fatalf("create intent malformed field error = %T %v, want validation error", err, err)
	}
}

func TestValidateNonPolymorphicLookupsCreateIntentRequiredWritePermissionSkipsParentPresence(t *testing.T) {
	ctx := t.Context()
	ps, _ := newPCOpportunityLineItemReproService()
	parentSchema := ps.metadata.GetSchema(ctx, "Opportunity")
	if parentSchema == nil {
		t.Fatal("Opportunity schema missing")
	}
	parentSchema.AccessPolicy = &models.ObjectAccessPolicy{
		Mode:       "derived",
		ObjectEdit: &models.AccessPolicyExpr{Kind: "recordFieldEqualsCurrentUser", Field: constants.FieldOwnerID},
	}
	schema := ps.metadata.GetSchema(ctx, "OpportunityLineItem")
	if schema == nil {
		t.Fatal("OpportunityLineItem schema missing")
	}
	schema.RequiredWritePermission = "EditTask"
	for i := range schema.Fields {
		if schema.Fields[i].APIName == "OpportunityId" {
			schema.Fields[i].Type = constants.FieldTypeReference
			schema.Fields[i].Required = false
			schema.Fields[i].IsMasterDetail = false
		}
	}

	if err := ps.validateNonPolymorphicLookups(
		withNonPolymorphicLookupCreateIntent(ctx),
		models.SObject{},
		schema,
		&models.UserSession{ID: "u1", ProfileID: "p1"},
	); err != nil {
		t.Fatalf("RequiredWritePermission-owned create should not require parent FK: %v", err)
	}
}

func TestValidateNonPolymorphicLookupsRequiredSharingParentUsesRecordContextEditPolicy(t *testing.T) {
	ctx := t.Context()
	user := &models.UserSession{ID: "u1", ProfileID: "p1"}
	parentPerm := &models.ObjectPermissions{
		SobjectType:               "Opportunity",
		PermissionsRead:           true,
		PermissionsViewAllRecords: true,
	}

	run := func(t *testing.T, parentOwner string, wantErr bool) {
		t.Helper()
		ps, repo := newPCOpportunityLineItemReproService()
		parentSchema := ps.metadata.GetSchema(ctx, "Opportunity")
		if parentSchema == nil {
			t.Fatal("Opportunity schema missing")
		}
		parentSchema.AccessPolicy = &models.ObjectAccessPolicy{
			ObjectRead: &models.AccessPolicyExpr{Kind: "systemPermission", APIName: "ReadOpportunityForLookup"},
			Mode:       "derived",
			ObjectEdit: &models.AccessPolicyExpr{Kind: "recordFieldEqualsCurrentUser", Field: constants.FieldOwnerID},
		}
		repo.findOneResult = models.SObject{
			constants.FieldID:      "opp1",
			constants.FieldOwnerID: parentOwner,
		}
		ps.permissions = permissions.NewPermissionService(&parentOnlyPermRepo{
			parentObject: "Opportunity",
			parentPerm:   parentPerm,
			systemPerms:  map[string]bool{"ReadOpportunityForLookup": true},
		}, ps.metadata, nil)

		childSchema := ps.metadata.GetSchema(ctx, "OpportunityLineItem")
		if childSchema == nil {
			t.Fatal("OpportunityLineItem schema missing")
		}
		err := ps.validateNonPolymorphicLookups(ctx, models.SObject{
			"OpportunityId": "opp1",
		}, childSchema, user)
		if wantErr {
			if err == nil {
				t.Fatal("validateNonPolymorphicLookups returned nil, want validation error")
			}
			if !appErrors.IsValidation(err) {
				t.Fatalf("validateNonPolymorphicLookups error = %T %v, want validation error", err, err)
			}
			return
		}
		if err != nil {
			t.Fatalf("validateNonPolymorphicLookups: %v", err)
		}
	}

	t.Run("parent record AP grants edit", func(t *testing.T) {
		run(t, "u1", false)
	})
	t.Run("parent record AP denies edit", func(t *testing.T) {
		run(t, "other", true)
	})
}

func TestPrelockNonPolymorphicLookupUpdateMissingTargetReturnsValidation(t *testing.T) {
	repo := newExclusiveSiblingRuntimeRepo(exclusiveSiblingRuntimeRows())
	ps := newExclusiveSiblingRuntimeService(repo)
	txm := &recordingBulkTxManager{}
	ps.txManager = txm

	err := ps.Update(exclusiveSiblingSystemContext(), "LineItem__c", "line-2", models.SObject{
		"ParentId": "missing-parent",
	}, recordEntrypointUser())

	assertMissingLookupTargetValidation(t, err)
	assertBulkPessimisticOnly(t, txm)
	assertNoEventPrefix(t, repo.events, "lock:LineItem__c:")
	assertNoEventPrefix(t, repo.events, "update:LineItem__c:")
	assertNoEventPrefix(t, repo.events, "reset:LineItem__c:")
}

func TestPrelockNonPolymorphicLookupCommitPreparedUpdateMissingTargetReturnsValidation(t *testing.T) {
	repo := newExclusiveSiblingRuntimeRepo(exclusiveSiblingRuntimeRows())
	ps := newExclusiveSiblingRuntimeService(repo)
	txm := &recordingBulkTxManager{}
	ps.txManager = txm

	err := ps.CommitPreparedUpdate(exclusiveSiblingSystemContext(), "LineItem__c", "line-2", &models.PreparedRecordUpdate{
		EffectiveUpdates: models.SObject{"ParentId": "missing-parent"},
	}, nil, recordEntrypointUser())

	assertMissingLookupTargetValidation(t, err)
	assertBulkPessimisticOnly(t, txm)
	assertNoEventPrefix(t, repo.events, "lock:LineItem__c:")
	assertNoEventPrefix(t, repo.events, "update:LineItem__c:")
	assertNoEventPrefix(t, repo.events, "reset:LineItem__c:")
}

func assertMissingLookupTargetValidation(t *testing.T, err error) {
	t.Helper()
	if err == nil {
		t.Fatal("missing lookup target returned nil error, want validation error")
	}
	if !appErrors.IsValidation(err) {
		t.Fatalf("missing lookup target error = %T %v, want validation error", err, err)
	}
	if appErrors.IsNotFound(err) {
		t.Fatalf("missing lookup target error = %T %v, did not want not-found", err, err)
	}
	want := `ParentId: referenced Parent__c record "missing-parent" is not accessible or does not exist`
	if !strings.Contains(err.Error(), want) {
		t.Fatalf("missing lookup target error = %q, want to contain %q", err.Error(), want)
	}
}
