package catalogsnapshot

import (
	"bytes"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	connector "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
	outboundintentv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
)

func TestOutboundIntentRoutesAreExactScopedAndImmutable(t *testing.T) {
	input := neutralCompileInput()
	authorship, finalization := connectorInboundFinalizationTestAuthority()
	input.MutationDefinitions = []MutationDefinitionInput{
		{CatalogRevision: testRevision, ConnectorInboundAuthorship: &authorship},
		{CatalogRevision: testRevision, ConnectorInboundFinalization: &finalization},
	}
	input.OutboundIntentRoutes = []OutboundIntentRouteInput{{
		CatalogRevision: testRevision,
		Route: OutboundIntentRoute{
			AuthorizedWorkloadID: "integration-service",
			ConnectorOwnerKey:    finalization.Contract.ConnectorOwnerKey,
			FlowDeveloperName:    finalization.Contract.FlowDeveloperName,
			FlowActionName:       finalization.Contract.FlowActionName,
			SourceObject:         finalization.Contract.SourceObject,
			ActionSlotID:         finalization.Contract.ActionSlotID,
			DestinationSlotID:    finalization.Contract.DestinationSlotID,
			Operation:            outboundintentv1.OperationUpsert,
			Bindings: []IntegrationBindingReference{
				{Kind: "CONNECTOR_PUSH_CONTRACT", OwnerPack: "example-connector-pack", PackInstallationID: "pack-installation-17", PackPayloadDigest: testPackPayloadDigest('a'), BusinessKey: "push/work-item", DefinitionVersion: 3},
				{Kind: "CONNECTOR_CALLOUT_PROFILE", OwnerPack: "example-profile-pack", PackInstallationID: "profile-installation-9", PackPayloadDigest: testPackPayloadDigest('b'), BusinessKey: "example-profile-pack/primary", DefinitionVersion: 2},
				{Kind: "CONNECTOR_FIELD_MAPPING", OwnerPack: "example-connector-pack", PackInstallationID: "pack-installation-17", PackPayloadDigest: testPackPayloadDigest('a'), BusinessKey: "mapping/work-item", DefinitionVersion: 1},
			},
		},
	}}
	input.Projections = append(input.Projections, RuntimeProjectionInput{
		CatalogRevision:   testRevision,
		AppInstallationID: testInstallA,
		CapabilityID:      "runtime.outbound-intents.consume.v1",
		Profile:           ProjectionRecordMutationV1,
		ObjectAPINames:    []string{"Unrelated", "WorkItem", "Workspace"},
	})

	snapshotArtifact := mustCompile(t, input)
	snapshot := mustOpenSnapshot(t, snapshotArtifact)
	routeBytes, err := marshalCanonical(snapshot.doc.OutboundIntentRoutes[0])
	if err != nil {
		t.Fatalf("marshal canonical UPSERT route: %v", err)
	}
	if snapshot.doc.Schema != CatalogSnapshotSchemaV15 || bytes.Contains(routeBytes, []byte(`"operation"`)) {
		t.Fatalf("explicit UPSERT changed legacy v15 wire grammar: schema=%q route=%s", snapshot.doc.Schema, routeBytes)
	}
	viewArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      "runtime.outbound-intents.consume.v1",
	})
	if err != nil {
		t.Fatalf("ProjectRuntimeMetadataView: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	routes := view.OutboundIntentRoutes()
	if len(routes) != 1 || routes[0].AuthorizedWorkloadID != "integration-service" || len(routes[0].Bindings) != 3 {
		t.Fatalf("routes = %#v", routes)
	}
	routes[0].Bindings[0].BusinessKey = "mutated"
	if got := view.OutboundIntentRoutes()[0].Bindings[0].BusinessKey; got == "mutated" {
		t.Fatal("returned outbound intent route aliases immutable view storage")
	}
	queryArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      testCapability,
	})
	if err != nil {
		t.Fatalf("project selected-root view: %v", err)
	}
	queryView := mustOpenView(t, queryArtifact)
	if len(queryView.OutboundIntentRoutes()) != 0 || bytes.Contains(queryArtifact.Bytes(), []byte("outboundIntentRoutes")) {
		t.Fatal("non-mutation projection leaked outbound intent authority")
	}
}

func TestOutboundMailIntentRouteCarriesNeutralCompletionAuthority(t *testing.T) {
	input := neutralCompileInput()
	input.OutboundIntentRoutes = []OutboundIntentRouteInput{{
		CatalogRevision: testRevision,
		Route: OutboundIntentRoute{
			AuthorizedWorkloadID: "integration-service",
			ConnectorOwnerKey:    "connected-mail",
			FlowDeveloperName:    "PackOwned_Send_Record_Message",
			FlowActionName:       "EnqueueMail",
			SourceObject:         "WorkItem",
			ActionSlotID:         "send-mail",
			DestinationSlotID:    "primary-mailbox",
			Operation:            outboundintentv1.OperationSendMail,
			Bindings: []IntegrationBindingReference{{
				Kind: "CONNECTOR_CALLOUT_PROFILE", OwnerPack: "mail-pack",
				PackInstallationID: "mail-installation", PackPayloadDigest: testPackPayloadDigest('c'),
				BusinessKey: "mail-pack/delivery", DefinitionVersion: 1,
			}},
			MailCompletion: neutralOutboundMailCompletionTestProjection(),
		},
	}}
	input.Projections = append(input.Projections, RuntimeProjectionInput{
		CatalogRevision: testRevision, AppInstallationID: testInstallA,
		CapabilityID: "runtime.outbound-mail.consume.v1", Profile: ProjectionRecordMutationV1,
		ObjectAPINames: []string{"Unrelated", "WorkItem", "Workspace"},
	})
	addNeutralOutboundMailTestMetadata(&input)

	snapshotArtifact := mustCompile(t, input)
	snapshot := mustOpenSnapshot(t, snapshotArtifact)
	if snapshot.doc.Schema != CatalogSnapshotSchemaV16 {
		t.Fatalf("mail Catalog schema = %q, want %q", snapshot.doc.Schema, CatalogSnapshotSchemaV16)
	}
	downgradedCatalog := bytes.Replace(snapshotArtifact.Bytes(), []byte(CatalogSnapshotSchemaV16), []byte(CatalogSnapshotSchemaV15), 1)
	if _, err := OpenCatalogSnapshot(makeArtifact(ArtifactCatalogSnapshot, downgradedCatalog)); err == nil {
		t.Fatal("v15 Catalog schema accepted typed mail authority")
	}

	effective, err := BuildEffectiveMetadata(input)
	if err != nil {
		t.Fatalf("BuildEffectiveMetadata: %v", err)
	}
	var effectiveDoc catalogDocument
	if err := decodeStrict(effective.Bytes(), &effectiveDoc); err != nil {
		t.Fatalf("decode effective metadata: %v", err)
	}
	if effectiveDoc.Schema != EffectiveMetadataSchemaV15 {
		t.Fatalf("mail effective metadata schema = %q, want %q", effectiveDoc.Schema, EffectiveMetadataSchemaV15)
	}
	downgradedEffective := bytes.Replace(effective.Bytes(), []byte(EffectiveMetadataSchemaV15), []byte(EffectiveMetadataSchemaV14), 1)
	if _, err := OpenEffectiveMetadata(ComputeDigest(downgradedEffective), downgradedEffective); err == nil {
		t.Fatal("v14 effective metadata schema accepted typed mail authority")
	}
	openedEffective, err := OpenEffectiveMetadata(effective.Digest(), effective.Bytes())
	if err != nil {
		t.Fatalf("OpenEffectiveMetadata: %v", err)
	}
	roundTripped, err := CompileEffectiveMetadata(openedEffective)
	if err != nil {
		t.Fatalf("CompileEffectiveMetadata: %v", err)
	}
	if roundTrippedSnapshot := mustOpenSnapshot(t, roundTripped); roundTrippedSnapshot.doc.Schema != CatalogSnapshotSchemaV16 {
		t.Fatalf("round-tripped mail Catalog schema = %q, want %q", roundTrippedSnapshot.doc.Schema, CatalogSnapshotSchemaV16)
	}

	viewArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA, CapabilityID: "runtime.outbound-mail.consume.v1",
	})
	if err != nil {
		t.Fatalf("ProjectRuntimeMetadataView: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	if view.doc.Schema != RuntimeMetadataViewSchemaV22 {
		t.Fatalf("mail Runtime view schema = %q, want %q", view.doc.Schema, RuntimeMetadataViewSchemaV22)
	}
	downgradedView := bytes.Replace(viewArtifact.Bytes(), []byte(RuntimeMetadataViewSchemaV22), []byte(RuntimeMetadataViewSchemaV17), 1)
	if _, err := OpenRuntimeMetadataView(makeArtifact(ArtifactRuntimeMetadataView, downgradedView)); err == nil {
		t.Fatal("v17 Runtime schema accepted typed mail authority")
	}
	routes := view.OutboundIntentRoutes()
	if len(routes) != 1 || routes[0].Operation != outboundintentv1.OperationSendMail || routes[0].MailCompletion == nil ||
		routes[0].MailCompletion.Activity.ObjectAPIName != "ActivityLog" ||
		routes[0].MailCompletion.Message.ObjectAPIName != "MessageLog" {
		t.Fatalf("mail route = %#v", routes)
	}
	routes[0].MailCompletion.Activity.ObjectAPIName = "Mutated"
	if got := view.OutboundIntentRoutes()[0].MailCompletion.Activity.ObjectAPIName; got == "Mutated" {
		t.Fatal("returned mail completion authority aliases immutable view storage")
	}
}

func TestOutboundMailV2AuthoritySelectsVersionedCatalogAndRuntimeSchemas(t *testing.T) {
	input := outboundMailFlowCompileInput()
	input.MutationDefinitions = append(input.MutationDefinitions, MutationDefinitionInput{
		CatalogRevision: testRevision,
		FieldMappingRule: &FieldMappingRule{
			SourceObject: "Workspace", TargetObject: "WorkItem", ActionType: "insertInheritFromLookup",
			SourceField: "Name", TargetField: "Subject", SourceLookupField: "WorkspaceId",
			ValueMap: `{"legacy":"Current"}`, OnUnmappedValue: connector.OnUnmappedValueSkip,
		},
	})
	completion := input.OutboundIntentRoutes[0].Route.MailCompletion
	completion.Message.CcAddressField = "CcAddress"
	completion.Message.BccAddressField = "BccAddress"
	completion.Message.HTMLBodyField = "HtmlBody"
	message := findOutboundMailTestObject(&input, "MessageLog")
	message.Fields = append(message.Fields,
		outboundMailTestField("message-cc", "CcAddress", constants.FieldTypeTextarea),
		outboundMailTestField("message-bcc", "BccAddress", constants.FieldTypeTextarea),
		outboundMailTestField("message-html", "HtmlBody", constants.FieldTypeRichtextarea),
	)

	snapshotArtifact := mustCompile(t, input)
	snapshot := mustOpenSnapshot(t, snapshotArtifact)
	if snapshot.doc.Schema != CatalogSnapshotSchemaV19 {
		t.Fatalf("mail v2 Catalog schema = %q, want %q", snapshot.doc.Schema, CatalogSnapshotSchemaV19)
	}
	downgradedCatalog := bytes.Replace(snapshotArtifact.Bytes(), []byte(CatalogSnapshotSchemaV19), []byte(CatalogSnapshotSchemaV16), 1)
	if _, err := OpenCatalogSnapshot(makeArtifact(ArtifactCatalogSnapshot, downgradedCatalog)); err == nil {
		t.Fatal("v16 Catalog schema accepted mail v2 authority")
	}
	effective, err := BuildEffectiveMetadata(input)
	if err != nil {
		t.Fatalf("BuildEffectiveMetadata: %v", err)
	}
	var effectiveDoc catalogDocument
	if err := decodeStrict(effective.Bytes(), &effectiveDoc); err != nil || effectiveDoc.Schema != EffectiveMetadataSchemaV18 {
		t.Fatalf("mail v2 effective metadata schema = %q, err=%v", effectiveDoc.Schema, err)
	}
	var mutationKey ProjectionKey
	for _, projection := range input.Projections {
		if projection.Profile == ProjectionRecordMutationV1 {
			mutationKey = ProjectionKey{
				AppInstallationID: projection.AppInstallationID, CapabilityID: projection.CapabilityID,
			}
			break
		}
	}
	viewArtifact, err := ProjectRuntimeMetadataView(snapshot, mutationKey)
	if err != nil {
		t.Fatalf("ProjectRuntimeMetadataView: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	if view.doc.Schema != RuntimeMetadataViewSchemaV26 {
		t.Fatalf("mail v2 Runtime schema = %q, want %q", view.doc.Schema, RuntimeMetadataViewSchemaV26)
	}
	rules := view.FieldMappingRulesForTarget("WorkItem")
	if len(rules) != 1 || rules[0].ValueMap != `{"legacy":"Current"}` ||
		rules[0].OnUnmappedValue != connector.OnUnmappedValueSkip {
		t.Fatalf("mail v2 Runtime lost field-mapping policies: %#v", rules)
	}
	downgradedView := bytes.Replace(viewArtifact.Bytes(), []byte(RuntimeMetadataViewSchemaV26), []byte(RuntimeMetadataViewSchemaV22), 1)
	if _, err := OpenRuntimeMetadataView(makeArtifact(ArtifactRuntimeMetadataView, downgradedView)); err == nil {
		t.Fatal("v22 Runtime schema accepted mail v2 authority")
	}
}

func TestOutboundMailV2AuthorityRejectsPartialCompletionShape(t *testing.T) {
	input := outboundMailFlowCompileInput()
	input.OutboundIntentRoutes[0].Route.MailCompletion.Message.CcAddressField = "CcAddress"
	if _, err := CompileCatalogSnapshot(input); err == nil {
		t.Fatal("partial mail v2 completion authority was accepted")
	}
}

func TestOutboundMailV3AuthorityRejectsAttachmentsWithoutV2Completion(t *testing.T) {
	input := outboundMailFlowCompileInput()
	input.OutboundIntentRoutes[0].Route.MailCompletion.Message.AttachmentIDsField = "AttachmentIds"
	if _, err := CompileCatalogSnapshot(input); err == nil {
		t.Fatal("attachment completion without Cc, Bcc, and HTML authority was accepted")
	}
}

func TestOutboundMailV2AuthorityRejectsRequiredOptionalCompletionFields(t *testing.T) {
	for _, fieldName := range []string{"CcAddress", "BccAddress", "HtmlBody"} {
		t.Run(fieldName, func(t *testing.T) {
			input := outboundMailFlowCompileInput()
			completion := input.OutboundIntentRoutes[0].Route.MailCompletion
			completion.Message.CcAddressField = "CcAddress"
			completion.Message.BccAddressField = "BccAddress"
			completion.Message.HTMLBodyField = "HtmlBody"
			message := findOutboundMailTestObject(&input, "MessageLog")
			message.Fields = append(message.Fields,
				outboundMailTestField("message-cc", "CcAddress", constants.FieldTypeTextarea),
				outboundMailTestField("message-bcc", "BccAddress", constants.FieldTypeTextarea),
				outboundMailTestField("message-html", "HtmlBody", constants.FieldTypeRichtextarea),
			)
			models.FindField(message, fieldName).Required = true

			if _, err := CompileCatalogSnapshot(input); err == nil {
				t.Fatalf("required optional completion field %s was accepted", fieldName)
			}
		})
	}
}

func TestOutboundMailIntentRoutesUseExactSourceScopedIdentity(t *testing.T) {
	input := neutralCompileInput()
	input.Projections = append(input.Projections, RuntimeProjectionInput{
		CatalogRevision: testRevision, AppInstallationID: testInstallA,
		CapabilityID: "runtime.outbound-mail.consume.v1", Profile: ProjectionRecordMutationV1,
		ObjectAPINames: []string{"Unrelated", "WorkItem", "Workspace"},
	})
	addNeutralOutboundMailTestMetadata(&input)
	unrelated := findOutboundMailTestObject(&input, "Unrelated")
	unrelated.Fields = append(unrelated.Fields,
		outboundMailTestField("unrelated-recipient", "RecipientAddress", constants.FieldTypeEmail),
		outboundMailTestField("unrelated-opt-out", "RecipientOptOut", constants.FieldTypeBoolean),
	)
	activitySource := models.FindField(
		findOutboundMailTestObject(&input, "ActivityLog"),
		"SourceRecordId",
	)
	activitySource.ReferenceTo = append(activitySource.ReferenceTo, "Unrelated")

	input.OutboundIntentRoutes = []OutboundIntentRouteInput{
		neutralOutboundMailRouteTestInput("WorkItem"),
		neutralOutboundMailRouteTestInput("Unrelated"),
	}
	snapshot := mustOpenSnapshot(t, mustCompile(t, input))
	if len(snapshot.doc.OutboundIntentRoutes) != 2 ||
		snapshot.doc.OutboundIntentRoutes[0].SourceObject != "Unrelated" ||
		snapshot.doc.OutboundIntentRoutes[1].SourceObject != "WorkItem" {
		t.Fatalf("source-scoped mail routes = %#v", snapshot.doc.OutboundIntentRoutes)
	}

	viewArtifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      "runtime.outbound-mail.consume.v1",
	})
	if err != nil {
		t.Fatal(err)
	}
	if routes := mustOpenView(t, viewArtifact).OutboundIntentRoutes(); len(routes) != 2 ||
		routes[0].SourceObject != "Unrelated" || routes[1].SourceObject != "WorkItem" {
		t.Fatalf("source-scoped Runtime mail routes = %#v", routes)
	}
}

func TestOutboundMailIntentRouteIdentityRejectsRuntimeAmbiguity(t *testing.T) {
	primary := neutralOutboundMailRouteTestInput("WorkItem")
	ambiguous := neutralOutboundMailRouteTestInput("WorkItem")
	ambiguous.Route.AuthorizedWorkloadID = "integration-secondary"
	ambiguous.Route.ActionSlotID = "send-mail-secondary"
	ambiguous.Route.DestinationSlotID = "secondary-mailbox"
	if _, err := canonicalOutboundIntentRoutes(
		testRevision,
		[]OutboundIntentRouteInput{primary, ambiguous},
	); err == nil {
		t.Fatal("Runtime-ambiguous SEND_MAIL routes were accepted")
	}
}

func TestOutboundMailSourceIdentityDoesNotWidenLegacyUpsertRoutes(t *testing.T) {
	workItem := neutralOutboundMailRouteTestInput("WorkItem")
	unrelated := neutralOutboundMailRouteTestInput("Unrelated")
	for _, input := range []*OutboundIntentRouteInput{&workItem, &unrelated} {
		input.Route.Operation = ""
		input.Route.MailCompletion = nil
		input.Route.Bindings[0].Kind = "CONNECTOR_PUSH_CONTRACT"
	}
	if _, err := canonicalOutboundIntentRoutes(
		testRevision,
		[]OutboundIntentRouteInput{workItem, unrelated},
	); err == nil {
		t.Fatal("sourceObject widened the legacy UPSERT route identity")
	}
}

func TestOutboundMailIntentRouteRejectsIncompleteOrProviderSelectedAuthority(t *testing.T) {
	valid := OutboundIntentRoute{
		AuthorizedWorkloadID: "integration-service", ConnectorOwnerKey: "connected-mail",
		FlowDeveloperName: "PackOwned_Send_Record_Message", FlowActionName: "EnqueueMail", SourceObject: "WorkItem",
		ActionSlotID: "send-mail", DestinationSlotID: "primary-mailbox", Operation: outboundintentv1.OperationSendMail,
		Bindings: []IntegrationBindingReference{{
			Kind: "CONNECTOR_CALLOUT_PROFILE", OwnerPack: "mail-pack", PackInstallationID: "mail-installation",
			PackPayloadDigest: testPackPayloadDigest('d'), BusinessKey: "mail-pack/delivery", DefinitionVersion: 1,
		}},
		MailCompletion: neutralOutboundMailCompletionTestProjection(),
	}
	for name, mutate := range map[string]func(*OutboundIntentRoute){
		"missing completion authority": func(route *OutboundIntentRoute) { route.MailCompletion = nil },
		"non-canonical operation": func(route *OutboundIntentRoute) {
			route.Operation = outboundintentv1.Operation("send_mail")
		},
		"provider endpoint as field": func(route *OutboundIntentRoute) {
			route.MailCompletion.RecipientAddressField = "https://provider.invalid/messages/send"
		},
		"provider credential binding": func(route *OutboundIntentRoute) {
			route.Bindings[0].Kind = "CONNECTOR_CREDENTIAL"
		},
		"multiple provider bindings": func(route *OutboundIntentRoute) {
			route.Bindings = append(route.Bindings, route.Bindings[0])
		},
		"same completion object": func(route *OutboundIntentRoute) {
			route.MailCompletion.Message.ObjectAPIName = route.MailCompletion.Activity.ObjectAPIName
		},
		"activity field alias": func(route *OutboundIntentRoute) {
			route.MailCompletion.Activity.BodyField = route.MailCompletion.Activity.SubjectField
		},
		"message field alias": func(route *OutboundIntentRoute) {
			route.MailCompletion.Message.TextBodyField = route.MailCompletion.Message.SubjectField
		},
		"record id field alias": func(route *OutboundIntentRoute) {
			route.MailCompletion.Message.SubjectField = constants.FieldID
		},
		"incoming completion message": func(route *OutboundIntentRoute) {
			route.MailCompletion.Message.FixedIncoming = true
		},
		"ambient fixed status": func(route *OutboundIntentRoute) {
			route.MailCompletion.Message.FixedStatus = " pending "
		},
	} {
		t.Run(name, func(t *testing.T) {
			route, err := cloneJSON(valid)
			if err != nil {
				t.Fatal(err)
			}
			mutate(&route)
			if err := validateOutboundIntentRoutes([]OutboundIntentRoute{route}, nil); err == nil {
				t.Fatal("invalid SEND_MAIL route was accepted")
			}
		})
	}
}

func TestValidateOutboundMailCompletionProjectionForLifecycleAdmission(t *testing.T) {
	if err := ValidateOutboundMailCompletionProjection(neutralOutboundMailCompletionTestProjection()); err != nil {
		t.Fatalf("valid completion projection: %v", err)
	}
	if err := ValidateOutboundMailCompletionProjection(nil); err == nil {
		t.Fatal("nil completion projection was accepted")
	}
	invalid := neutralOutboundMailCompletionTestProjection()
	invalid.Message.TextBodyField = invalid.Message.SubjectField
	if err := ValidateOutboundMailCompletionProjection(invalid); err == nil {
		t.Fatal("aliased completion fields were accepted")
	}
}

func TestOutboundMailIntentRouteRejectsAbsentOrIncompatibleMetadata(t *testing.T) {
	for name, mutate := range map[string]func(*CompileInput){
		"absent completion object": func(input *CompileInput) {
			input.Objects = input.Objects[:len(input.Objects)-1]
		},
		"incompatible recipient opt-out type": func(input *CompileInput) {
			models.FindField(findOutboundMailTestObject(input, "WorkItem"), "RecipientOptOut").Type = constants.FieldTypeString
		},
		"non-createable completion field": func(input *CompileInput) {
			models.FindField(findOutboundMailTestObject(input, "MessageLog"), "Status").Createable = false
		},
		"wrong activity reference target": func(input *CompileInput) {
			models.FindField(findOutboundMailTestObject(input, "ActivityLog"), "SourceRecordId").ReferenceTo = []string{"Workspace"}
		},
	} {
		t.Run(name, func(t *testing.T) {
			input := neutralCompileInput()
			input.OutboundIntentRoutes = []OutboundIntentRouteInput{{
				CatalogRevision: testRevision,
				Route: OutboundIntentRoute{
					AuthorizedWorkloadID: "integration-service", ConnectorOwnerKey: "connected-mail",
					FlowDeveloperName: "PackOwned_Send_Record_Message", FlowActionName: "EnqueueMail",
					SourceObject: "WorkItem", ActionSlotID: "send-mail", DestinationSlotID: "primary-mailbox",
					Operation: outboundintentv1.OperationSendMail,
					Bindings: []IntegrationBindingReference{{
						Kind: "CONNECTOR_CALLOUT_PROFILE", OwnerPack: "mail-pack", PackInstallationID: "mail-installation",
						PackPayloadDigest: testPackPayloadDigest('9'), BusinessKey: "mail-pack/delivery", DefinitionVersion: 1,
					}},
					MailCompletion: neutralOutboundMailCompletionTestProjection(),
				},
			}}
			addNeutralOutboundMailTestMetadata(&input)
			mutate(&input)
			artifact, err := CompileCatalogSnapshot(input)
			if err == nil {
				_, err = OpenCatalogSnapshot(artifact)
			}
			if err == nil {
				t.Fatal("Catalog accepted unresolvable outbound mail completion metadata")
			}
		})
	}
}

func TestOutboundUpsertIntentRouteRejectsMailCompletionAuthority(t *testing.T) {
	_, finalization := connectorInboundFinalizationTestAuthority()
	route := OutboundIntentRoute{
		ConnectorOwnerKey: finalization.Contract.ConnectorOwnerKey,
		FlowDeveloperName: finalization.Contract.FlowDeveloperName,
		FlowActionName:    finalization.Contract.FlowActionName,
		SourceObject:      finalization.Contract.SourceObject,
		ActionSlotID:      finalization.Contract.ActionSlotID,
		DestinationSlotID: finalization.Contract.DestinationSlotID,
		MailCompletion:    neutralOutboundMailCompletionTestProjection(),
		Bindings: []IntegrationBindingReference{{
			Kind: "CONNECTOR_PUSH_CONTRACT", OwnerPack: "connector-pack", PackInstallationID: "installation",
			PackPayloadDigest: testPackPayloadDigest('e'), BusinessKey: "push/work-item", DefinitionVersion: 1,
		}},
		AuthorizedWorkloadID: "integration-service",
	}
	mutation := &mutationDefinitionDocument{ConnectorInboundFinalization: []ConnectorInboundFinalizationAuthority{finalization}}
	if err := validateOutboundIntentRoutes([]OutboundIntentRoute{route}, mutation); err == nil {
		t.Fatal("legacy UPSERT route with mail completion authority was accepted")
	}
}

func TestOutboundIntentRoutesAndAuthoritiesRemainAlignedForMultipleSourceObjects(t *testing.T) {
	input := neutralCompileInput()
	for index := range input.Objects {
		if input.Objects[index].Metadata.APIName == "Unrelated" {
			input.Objects[index].Metadata.Fields = append(input.Objects[index].Metadata.Fields,
				models.FieldMetadata{
					ID: "field-unrelated-name", APIName: "Name", Label: "Name",
					Type: constants.FieldTypeString,
				},
			)
		}
	}
	workAuthorship, workFinalization := connectorInboundFinalizationTestAuthority()
	unrelatedFinalization, err := cloneJSON(workFinalization)
	if err != nil {
		t.Fatal(err)
	}
	unrelatedFinalization.Contract.FlowDeveloperName = "Unrelated_To_Remote"
	unrelatedFinalization.Contract.FlowActionName = "PushUnrelated"
	unrelatedFinalization.Contract.SourceObject = "Unrelated"
	unrelatedFinalization.Contract.ActionSlotID = "remote-unrelated-upsert"
	unrelatedFinalization.Contract.DestinationSlotID = "remote-unrelated"
	unrelatedFinalization.Contract.RemoteIDField = "Id"
	unrelatedFinalization.OutboundMappings[0].SourceObject = "Unrelated"
	unrelatedFinalization.OutboundMappings[0].SourceField = "Name"
	unrelatedAuthorship := connectorInboundAuthorshipForFinalization(unrelatedFinalization)
	input.MutationDefinitions = []MutationDefinitionInput{
		{CatalogRevision: testRevision, ConnectorInboundAuthorship: &workAuthorship},
		{CatalogRevision: testRevision, ConnectorInboundFinalization: &workFinalization},
		{CatalogRevision: testRevision, ConnectorInboundAuthorship: &unrelatedAuthorship},
		{CatalogRevision: testRevision, ConnectorInboundFinalization: &unrelatedFinalization},
	}
	routeFor := func(finalization ConnectorInboundFinalizationAuthority, businessKey string) OutboundIntentRouteInput {
		contract := finalization.Contract
		return OutboundIntentRouteInput{
			CatalogRevision: testRevision,
			Route: OutboundIntentRoute{
				AuthorizedWorkloadID: "integration-service",
				ConnectorOwnerKey:    contract.ConnectorOwnerKey,
				FlowDeveloperName:    contract.FlowDeveloperName,
				FlowActionName:       contract.FlowActionName,
				SourceObject:         contract.SourceObject,
				ActionSlotID:         contract.ActionSlotID,
				DestinationSlotID:    contract.DestinationSlotID,
				Bindings: []IntegrationBindingReference{{
					Kind: "CONNECTOR_PUSH_CONTRACT", OwnerPack: contract.ConnectorOwnerKey,
					PackInstallationID: "pack-installation-17", PackPayloadDigest: testPackPayloadDigest('a'),
					BusinessKey: businessKey, DefinitionVersion: 1,
				}},
			},
		}
	}
	input.OutboundIntentRoutes = []OutboundIntentRouteInput{
		routeFor(workFinalization, "push/work-item"),
		routeFor(unrelatedFinalization, "push/unrelated"),
	}
	input.Projections = append(input.Projections, RuntimeProjectionInput{
		CatalogRevision: testRevision, AppInstallationID: testInstallA,
		CapabilityID: "runtime.outbound-multiple.v1", Profile: ProjectionRecordMutationV1,
		ObjectAPINames: []string{"Unrelated", "WorkItem", "Workspace"},
	})

	artifact := mustCompile(t, input)
	viewArtifact, err := ProjectRuntimeMetadataView(mustOpenSnapshot(t, artifact), ProjectionKey{
		AppInstallationID: testInstallA, CapabilityID: "runtime.outbound-multiple.v1",
	})
	if err != nil {
		t.Fatalf("ProjectRuntimeMetadataView: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	if len(view.OutboundIntentRoutes()) != 2 ||
		len(view.ConnectorInboundFinalizationAuthorities(workFinalization.Contract.ConnectorOwnerKey)) != 2 {
		t.Fatalf("multi-source closure = routes %#v authorities %#v", view.OutboundIntentRoutes(),
			view.ConnectorInboundFinalizationAuthorities(workFinalization.Contract.ConnectorOwnerKey))
	}
}

func TestOutboundIntentRouteRejectsMixedRevisionOrMissingAuthority(t *testing.T) {
	base := neutralCompileInput()
	base.OutboundIntentRoutes = []OutboundIntentRouteInput{{
		CatalogRevision: testRevision - 1,
		Route:           OutboundIntentRoute{AuthorizedWorkloadID: "integration-service"},
	}}
	if _, err := CompileCatalogSnapshot(base); err == nil {
		t.Fatal("CompileCatalogSnapshot accepted mixed route revision")
	}

	authorship, finalization := connectorInboundFinalizationTestAuthority()
	base.OutboundIntentRoutes[0] = OutboundIntentRouteInput{
		CatalogRevision: testRevision,
		Route: OutboundIntentRoute{
			AuthorizedWorkloadID: "integration-service",
			ConnectorOwnerKey:    finalization.Contract.ConnectorOwnerKey,
			FlowDeveloperName:    "wrong-flow",
			FlowActionName:       finalization.Contract.FlowActionName,
			SourceObject:         finalization.Contract.SourceObject,
			ActionSlotID:         finalization.Contract.ActionSlotID,
			DestinationSlotID:    finalization.Contract.DestinationSlotID,
			Bindings:             []IntegrationBindingReference{{Kind: "CONNECTOR_PUSH_CONTRACT", OwnerPack: "example-connector-pack", PackInstallationID: "pack-installation-17", PackPayloadDigest: testPackPayloadDigest('a'), BusinessKey: "push/work-item", DefinitionVersion: 1}},
		},
	}
	base.MutationDefinitions = []MutationDefinitionInput{
		{CatalogRevision: testRevision, ConnectorInboundAuthorship: &authorship},
		{CatalogRevision: testRevision, ConnectorInboundFinalization: &finalization},
	}
	if _, err := CompileCatalogSnapshot(base); err == nil {
		t.Fatal("CompileCatalogSnapshot accepted route without exact execution authority")
	}
}

func TestOutboundIntentRouteActivatesOnlyExactPackOwnedAfterSaveFlow(t *testing.T) {
	base := outboundIntentFlowCompileInput()
	artifact := mustCompile(t, base)
	viewArtifact, err := ProjectRuntimeMetadataView(mustOpenSnapshot(t, artifact), ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      testMutationCapability,
	})
	if err != nil {
		t.Fatalf("project exact outbound intent Flow: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	if len(view.OutboundIntentRoutes()) != 1 || len(view.doc.Mutation.FlowVersions) != 1 {
		t.Fatalf("exact outbound closure = routes %#v mutation %#v", view.OutboundIntentRoutes(), view.doc.Mutation)
	}

	withSameIdentityMailRoute := outboundIntentFlowCompileInput()
	legacyRoute := withSameIdentityMailRoute.OutboundIntentRoutes[0].Route
	withSameIdentityMailRoute.OutboundIntentRoutes = append(withSameIdentityMailRoute.OutboundIntentRoutes, OutboundIntentRouteInput{
		CatalogRevision: testRevision,
		Route: OutboundIntentRoute{
			AuthorizedWorkloadID: legacyRoute.AuthorizedWorkloadID,
			ConnectorOwnerKey:    legacyRoute.ConnectorOwnerKey, FlowDeveloperName: legacyRoute.FlowDeveloperName,
			FlowActionName: legacyRoute.FlowActionName, SourceObject: legacyRoute.SourceObject,
			ActionSlotID: legacyRoute.ActionSlotID, DestinationSlotID: legacyRoute.DestinationSlotID,
			Operation: outboundintentv1.OperationSendMail,
			Bindings: []IntegrationBindingReference{{
				Kind: "CONNECTOR_CALLOUT_PROFILE", OwnerPack: legacyRoute.ConnectorOwnerKey,
				PackInstallationID: "mail-profile-installation", PackPayloadDigest: testPackPayloadDigest('f'),
				BusinessKey: "mail/delivery", DefinitionVersion: 1,
			}},
			MailCompletion: neutralOutboundMailCompletionTestProjection(),
		},
	})
	addNeutralOutboundMailTestMetadata(&withSameIdentityMailRoute)
	mailArtifact := mustCompile(t, withSameIdentityMailRoute)
	mailViewArtifact, err := ProjectRuntimeMetadataView(mustOpenSnapshot(t, mailArtifact), ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      testMutationCapability,
	})
	if err != nil {
		t.Fatalf("project same-identity mail route: %v", err)
	}
	mailView := mustOpenView(t, mailViewArtifact)
	if len(mailView.OutboundIntentRoutes()) != 2 || len(mailView.doc.Mutation.FlowVersions) != 1 {
		t.Fatalf("same-identity mail route changed UPSERT Flow admission: routes=%#v mutation=%#v", mailView.OutboundIntentRoutes(), mailView.doc.Mutation)
	}

	for name, mutate := range map[string]func(*CompileInput){
		"before-save": func(input *CompileInput) {
			input.MutationDefinitions[0].TriggerSubscription.EventType = "RecordBeforeSave"
			input.MutationDefinitions[1].FlowDefinition.TriggerType = "RecordBeforeSave"
			input.MutationDefinitions[2].FlowVersion.MetadataRaw = []byte(`{"status":"Active","start":{"object":"WorkItem","triggerType":"RecordBeforeSave"},"actionCalls":[{"name":"PushWorkItem","actionType":"PushToConnector","locationX":0,"locationY":0}]}`)
		},
		"customized": func(input *CompileInput) {
			input.MutationDefinitions[1].FlowDefinition.IsCustomized = true
		},
		"missing pack owner": func(input *CompileInput) {
			input.MutationDefinitions[1].FlowDefinition.OwnerPack = ""
		},
		"missing exact route": func(input *CompileInput) {
			input.OutboundIntentRoutes = nil
		},
	} {
		t.Run(name, func(t *testing.T) {
			input := outboundIntentFlowCompileInput()
			mutate(&input)
			if _, err := CompileCatalogSnapshot(input); err == nil {
				t.Fatal("Catalog activated an outbound intent Flow outside exact after-save route authority")
			}
		})
	}
}

func TestOutboundMailIntentRouteActivatesOnlyExactPackOwnedAfterSaveFlow(t *testing.T) {
	input := outboundMailFlowCompileInput()
	artifact := mustCompile(t, input)
	viewArtifact, err := ProjectRuntimeMetadataView(mustOpenSnapshot(t, artifact), ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      testMutationCapability,
	})
	if err != nil {
		t.Fatalf("project exact outbound mail Flow: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	if len(view.OutboundIntentRoutes()) != 1 || len(view.doc.Mutation.FlowVersions) != 1 {
		t.Fatalf("exact outbound mail closure = routes %#v mutation %#v", view.OutboundIntentRoutes(), view.doc.Mutation)
	}

	for name, mutate := range map[string]func(*CompileInput){
		"upsert route": func(input *CompileInput) {
			route := &input.OutboundIntentRoutes[0].Route
			route.Operation = outboundintentv1.OperationUpsert
			route.MailCompletion = nil
			route.Bindings = []IntegrationBindingReference{{
				Kind: "CONNECTOR_PUSH_CONTRACT", OwnerPack: route.ConnectorOwnerKey,
				PackInstallationID: "pack-installation-17", PackPayloadDigest: testPackPayloadDigest('a'),
				BusinessKey: "push/work-item", DefinitionVersion: 1,
			}}
		},
		"push action": func(input *CompileInput) {
			input.MutationDefinitions[2].FlowVersion.MetadataRaw = []byte(`{"status":"Active","start":{"object":"WorkItem","triggerType":"RecordAfterSave"},"actionCalls":[{"name":"PushWorkItem","actionType":"PushToConnector","locationX":0,"locationY":0}]}`)
		},
		"customized": func(input *CompileInput) {
			input.MutationDefinitions[1].FlowDefinition.IsCustomized = true
		},
		"missing exact route": func(input *CompileInput) {
			input.OutboundIntentRoutes = nil
		},
	} {
		t.Run(name, func(t *testing.T) {
			candidate := outboundMailFlowCompileInput()
			mutate(&candidate)
			if _, err := CompileCatalogSnapshot(candidate); err == nil {
				t.Fatal("Catalog activated an outbound mail Flow outside exact SEND_MAIL route authority")
			}
		})
	}
}

func TestOutboundMailV3IntentRouteUsesTheSameExactSendMailAuthority(t *testing.T) {
	input := outboundMailFlowCompileInput()
	input.MutationDefinitions[2].FlowVersion.MetadataRaw = []byte(`{"status":"Active","start":{"object":"WorkItem","triggerType":"RecordAfterSave"},"actionCalls":[{"name":"PushWorkItem","actionType":"enqueueOutboundMailV3","locationX":0,"locationY":0}]}`)
	artifact := mustCompile(t, input)
	viewArtifact, err := ProjectRuntimeMetadataView(mustOpenSnapshot(t, artifact), ProjectionKey{
		AppInstallationID: testInstallA,
		CapabilityID:      testMutationCapability,
	})
	if err != nil {
		t.Fatalf("project exact outbound mail V3 Flow: %v", err)
	}
	view := mustOpenView(t, viewArtifact)
	if len(view.OutboundIntentRoutes()) != 1 || len(view.doc.Mutation.FlowVersions) != 1 {
		t.Fatalf("exact outbound mail V3 closure = routes %#v mutation %#v", view.OutboundIntentRoutes(), view.doc.Mutation)
	}
}

func TestOutboundIntentRouteRejectsJunctionAssociationMechanism(t *testing.T) {
	_, finalization := connectorInboundFinalizationTestAuthority()
	finalization.Contract.Associations = []connector.ConnectorPushAssociationContract{{
		Junction: &connector.ConnectorPushAssociationJunctionContract{
			Object: "WorkItem", SourceLookupField: "WorkspaceId", TargetLookupField: "UnrelatedId", Limit: 1,
		},
	}}
	route := OutboundIntentRoute{
		ConnectorOwnerKey: finalization.Contract.ConnectorOwnerKey,
		FlowDeveloperName: finalization.Contract.FlowDeveloperName,
		FlowActionName:    finalization.Contract.FlowActionName,
		SourceObject:      finalization.Contract.SourceObject,
		ActionSlotID:      finalization.Contract.ActionSlotID,
		DestinationSlotID: finalization.Contract.DestinationSlotID,
	}
	mutation := &mutationDefinitionDocument{ConnectorInboundFinalization: []ConnectorInboundFinalizationAuthority{finalization}}
	if outboundIntentRouteHasFinalizationAuthority(route, mutation) {
		t.Fatal("outbound intent route accepted a junction association that the neutral payload mechanism cannot derive")
	}
}

func outboundIntentFlowCompileInput() CompileInput {
	input := mutationCompileInput()
	authorship, finalization := connectorInboundFinalizationTestAuthority()
	input.MutationDefinitions = []MutationDefinitionInput{
		{CatalogRevision: testRevision, TriggerSubscription: &TriggerSubscription{
			ID: "trigger-outbound", KeyPrefix: "0WI", EventType: "RecordAfterSave",
			RecordTriggerType: "Create", FlowDefinitionID: "flow-outbound", FlowVersionID: "flow-outbound-v1", IsActive: true,
		}},
		{CatalogRevision: testRevision, FlowDefinition: &models.FlowDefinition{
			Id: "flow-outbound", DeveloperName: finalization.Contract.FlowDeveloperName,
			ProcessType: "AutoLaunchedFlow", TriggerType: "RecordAfterSave", TriggerObjectOrEvent: "WorkItem",
			ActiveVersionId: "flow-outbound-v1", OwnerPack: finalization.Contract.ConnectorOwnerKey,
		}},
		{CatalogRevision: testRevision, FlowVersion: &models.FlowVersion{
			Id: "flow-outbound-v1", DefinitionId: "flow-outbound", VersionNumber: 1,
			Status: models.FlowStatusActive, ProcessType: "AutoLaunchedFlow", Source: "standard",
			MetadataRaw: []byte(`{"status":"Active","start":{"object":"WorkItem","triggerType":"RecordAfterSave"},"actionCalls":[{"name":"PushWorkItem","actionType":"PushToConnector","locationX":0,"locationY":0}]}`),
		}},
		{CatalogRevision: testRevision, ConnectorInboundAuthorship: &authorship},
		{CatalogRevision: testRevision, ConnectorInboundFinalization: &finalization},
	}
	input.OutboundIntentRoutes = []OutboundIntentRouteInput{{
		CatalogRevision: testRevision,
		Route: OutboundIntentRoute{
			AuthorizedWorkloadID: "integration-service",
			ConnectorOwnerKey:    finalization.Contract.ConnectorOwnerKey,
			FlowDeveloperName:    finalization.Contract.FlowDeveloperName,
			FlowActionName:       finalization.Contract.FlowActionName,
			SourceObject:         finalization.Contract.SourceObject,
			ActionSlotID:         finalization.Contract.ActionSlotID,
			DestinationSlotID:    finalization.Contract.DestinationSlotID,
			Bindings: []IntegrationBindingReference{{
				Kind: "CONNECTOR_PUSH_CONTRACT", OwnerPack: finalization.Contract.ConnectorOwnerKey,
				PackInstallationID: "pack-installation-17", PackPayloadDigest: testPackPayloadDigest('a'),
				BusinessKey: "push/work-item", DefinitionVersion: 1,
			}},
		},
	}}
	return input
}

func outboundMailFlowCompileInput() CompileInput {
	input := outboundIntentFlowCompileInput()
	input.MutationDefinitions[2].FlowVersion.MetadataRaw = []byte(`{"status":"Active","start":{"object":"WorkItem","triggerType":"RecordAfterSave"},"actionCalls":[{"name":"PushWorkItem","actionType":"enqueueOutboundMail","locationX":0,"locationY":0}]}`)
	route := &input.OutboundIntentRoutes[0].Route
	route.Operation = outboundintentv1.OperationSendMail
	route.Bindings = []IntegrationBindingReference{{
		Kind: "CONNECTOR_CALLOUT_PROFILE", OwnerPack: route.ConnectorOwnerKey,
		PackInstallationID: "mail-profile-installation", PackPayloadDigest: testPackPayloadDigest('f'),
		BusinessKey: "mail/delivery", DefinitionVersion: 1,
	}}
	route.MailCompletion = neutralOutboundMailCompletionTestProjection()
	addNeutralOutboundMailTestMetadata(&input)
	return input
}

func testPackPayloadDigest(value byte) string {
	return "nexus-pack-sha256-v1:" + string(bytes.Repeat([]byte{value}, 64))
}

func neutralOutboundMailCompletionTestProjection() *OutboundMailCompletionProjection {
	return &OutboundMailCompletionProjection{
		RecipientAddressField: "RecipientAddress", RecipientOptOutField: "RecipientOptOut",
		Activity: OutboundMailActivityProjection{
			ObjectAPIName: "ActivityLog", SourceRecordField: "SourceRecordId", SubjectField: "Subject",
			ActivityDateField: "ActivityDate", StatusField: "Status", PriorityField: "Priority",
			BodyField: "Body", OwnerField: "OwnerId", TypeField: "Type", SubtypeField: "Subtype",
			FixedSubject: "Mail", FixedStatus: "Completed", FixedPriority: "Normal",
			FixedType: "Mail", FixedSubtype: "Outbound",
		},
		Message: OutboundMailMessageProjection{
			ObjectAPIName: "MessageLog", ActivityIDField: "ActivityId", FromAddressField: "FromAddress",
			ToAddressField: "ToAddress", SubjectField: "Subject", TextBodyField: "TextBody",
			IncomingField: "Incoming", StatusField: "Status", MessageDateField: "MessageDate",
			FixedIncoming: false, FixedStatus: "Delivered",
		},
	}
}

func neutralOutboundMailRouteTestInput(sourceObject string) OutboundIntentRouteInput {
	return OutboundIntentRouteInput{
		CatalogRevision: testRevision,
		Route: OutboundIntentRoute{
			AuthorizedWorkloadID: "integration-service", ConnectorOwnerKey: "connected-mail",
			FlowDeveloperName: "PackOwned_Send_Record_Message", FlowActionName: "EnqueueMail",
			SourceObject: sourceObject, ActionSlotID: "send-mail", DestinationSlotID: "primary-mailbox",
			Operation: outboundintentv1.OperationSendMail,
			Bindings: []IntegrationBindingReference{{
				Kind: "CONNECTOR_CALLOUT_PROFILE", OwnerPack: "mail-pack",
				PackInstallationID: "mail-installation", PackPayloadDigest: testPackPayloadDigest('f'),
				BusinessKey: "mail-pack/delivery", DefinitionVersion: 1,
			}},
			MailCompletion: neutralOutboundMailCompletionTestProjection(),
		},
	}
}

func addNeutralOutboundMailTestMetadata(input *CompileInput) {
	workItem := findOutboundMailTestObject(input, "WorkItem")
	workItem.Fields = append(workItem.Fields,
		outboundMailTestField("work-item-recipient", "RecipientAddress", constants.FieldTypeEmail),
		outboundMailTestField("work-item-opt-out", "RecipientOptOut", constants.FieldTypeBoolean),
	)
	input.Objects = append(input.Objects,
		ObjectInput{CatalogRevision: testRevision, Metadata: &models.ObjectMetadata{
			ID: "obj-activity-log", APIName: "ActivityLog", Label: "Activity Log", PluralLabel: "Activity Logs",
			SharingModel: constants.SharingModelPublicReadWrite,
			Fields: []models.FieldMetadata{
				outboundMailTestReferenceField("activity-source", "SourceRecordId", "WorkItem"),
				outboundMailTestField("activity-subject", "Subject", constants.FieldTypeString),
				outboundMailTestField("activity-date", "ActivityDate", constants.FieldTypeDate),
				outboundMailTestField("activity-status", "Status", constants.FieldTypeString),
				outboundMailTestField("activity-priority", "Priority", constants.FieldTypeString),
				outboundMailTestField("activity-body", "Body", constants.FieldTypeTextarea),
				outboundMailTestField("activity-owner", "OwnerId", constants.FieldTypeReference),
				outboundMailTestField("activity-type", "Type", constants.FieldTypeString),
				outboundMailTestField("activity-subtype", "Subtype", constants.FieldTypeString),
			},
		}},
		ObjectInput{CatalogRevision: testRevision, Metadata: &models.ObjectMetadata{
			ID: "obj-message-log", APIName: "MessageLog", Label: "Message Log", PluralLabel: "Message Logs",
			SharingModel: constants.SharingModelPublicReadWrite,
			Fields: []models.FieldMetadata{
				outboundMailTestReferenceField("message-activity", "ActivityId", "ActivityLog"),
				outboundMailTestField("message-from", "FromAddress", constants.FieldTypeEmail),
				outboundMailTestField("message-to", "ToAddress", constants.FieldTypeEmail),
				outboundMailTestField("message-subject", "Subject", constants.FieldTypeString),
				outboundMailTestField("message-body", "TextBody", constants.FieldTypeTextarea),
				outboundMailTestField("message-incoming", "Incoming", constants.FieldTypeBoolean),
				outboundMailTestField("message-status", "Status", constants.FieldTypeString),
				outboundMailTestField("message-date", "MessageDate", constants.FieldTypeDatetime),
			},
		}},
	)
	for index := range input.Projections {
		if input.Projections[index].Profile == ProjectionRecordMutationV1 {
			input.Projections[index].ObjectAPINames = append(input.Projections[index].ObjectAPINames, "ActivityLog", "MessageLog")
		}
	}
}

func findOutboundMailTestObject(input *CompileInput, name string) *models.ObjectMetadata {
	for index := range input.Objects {
		if input.Objects[index].Metadata.APIName == name {
			return input.Objects[index].Metadata
		}
	}
	return nil
}

func outboundMailTestField(id, name string, fieldType constants.SchemaFieldType) models.FieldMetadata {
	return models.FieldMetadata{ID: id, APIName: name, Label: name, Type: fieldType, Createable: true}
}

func outboundMailTestReferenceField(id, name, target string) models.FieldMetadata {
	field := outboundMailTestField(id, name, constants.FieldTypeReference)
	field.ReferenceTo = []string{target}
	return field
}
