package connector_outboundintent

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"reflect"
	"testing"
	"time"

	connector_mail "github.com/pingcap-inc/nexuscrm-connectors/features/connector/mail"
	connector_oauth "github.com/pingcap-inc/nexuscrm-connectors/features/connector/oauth"
	connector_throttle "github.com/pingcap-inc/nexuscrm-connectors/features/connector/throttle"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	commonv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	outboundintentv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
)

type mailAuthorityStub struct {
	binding         MailBinding
	credential      *connector_throttle.ResolvedCredential
	err             error
	revalidateErr   error
	calls           int
	revalidateCalls int
	revalidated     ResolvedMailBinding
}

func (stub *mailAuthorityStub) ResolveMail(context.Context, Command) (MailBinding, error) {
	stub.calls++
	return stub.binding, stub.err
}

func (stub *mailAuthorityStub) ResolveResolvedMail(
	context.Context,
	Command,
) (ResolvedMailBinding, error) {
	stub.calls++
	credential := testResolvedMailCredential()
	if stub.credential != nil {
		credential = *stub.credential
	}
	return ResolvedMailBinding{MailBinding: stub.binding, Credential: credential}, stub.err
}

func (stub *mailAuthorityStub) RevalidateResolvedMail(
	_ context.Context,
	binding ResolvedMailBinding,
) error {
	stub.revalidateCalls++
	stub.revalidated = binding
	stub.revalidated.RequiredScopes = append([]string(nil), binding.RequiredScopes...)
	return stub.revalidateErr
}

type mailOAuthStub struct {
	principal      *connector_oauth.ConnectedPrincipal
	principalErr   error
	accessToken    string
	accessTokenErr []error
	principalCalls int
	tokenCalls     int
	lastRequest    connector_oauth.ResolveScopedAccessTokenRequest
}

func (stub *mailOAuthStub) Principal(context.Context, string, string) (*connector_oauth.ConnectedPrincipal, error) {
	stub.principalCalls++
	return stub.principal, stub.principalErr
}

func (stub *mailOAuthStub) ResolveScopedAccessToken(
	_ context.Context,
	request connector_oauth.ResolveScopedAccessTokenRequest,
) (string, error) {
	stub.lastRequest = request
	index := stub.tokenCalls
	stub.tokenCalls++
	if index < len(stub.accessTokenErr) && stub.accessTokenErr[index] != nil {
		return "", stub.accessTokenErr[index]
	}
	return stub.accessToken, nil
}

type mailProviderResolverStub struct {
	provider connector_mail.Provider
	key      string
	calls    int
}

func (stub *mailProviderResolverStub) MailProvider(key string) (connector_mail.Provider, bool) {
	stub.calls++
	if stub.provider == nil || stub.key != "" && stub.key != key {
		return nil, false
	}
	return stub.provider, true
}

type mailProviderStub struct {
	bound     time.Duration
	result    connector_mail.DispatchResult
	err       error
	dispatch  func(context.Context) (connector_mail.DispatchResult, error)
	calls     int
	token     string
	message   connector_mail.Message
	messageV2 connector_mail.MessageV2
	messageV3 connector_mail.MessageV3
}

type mailProviderV1OnlyStub struct {
	result connector_mail.DispatchResult
	calls  int
}

func (stub *mailProviderV1OnlyStub) Dispatch(
	_ context.Context,
	_ string,
	_ connector_mail.Message,
) (connector_mail.DispatchResult, error) {
	stub.calls++
	return stub.result, nil
}

func (stub *mailProviderV1OnlyStub) EffectTerminationBound() time.Duration {
	return time.Second
}

func (stub *mailProviderStub) DispatchV3(
	ctx context.Context,
	token string,
	message connector_mail.MessageV3,
) (connector_mail.DispatchResult, error) {
	stub.calls++
	stub.token = token
	stub.messageV3 = message
	stub.messageV3.Attachments = make([]connector_mail.Attachment, len(message.Attachments))
	for index, attachment := range message.Attachments {
		stub.messageV3.Attachments[index] = connector_mail.Attachment{
			FileName: attachment.FileName, ContentType: attachment.ContentType,
			Data: append([]byte(nil), attachment.Data...),
		}
	}
	if stub.dispatch != nil {
		return stub.dispatch(ctx)
	}
	return stub.result, stub.err
}

func (stub *mailProviderStub) DispatchV2(
	ctx context.Context,
	token string,
	message connector_mail.MessageV2,
) (connector_mail.DispatchResult, error) {
	stub.calls++
	stub.token = token
	stub.messageV2 = message
	stub.messageV2.Message.ToAddresses = append([]string(nil), message.Message.ToAddresses...)
	stub.messageV2.CcAddresses = append([]string(nil), message.CcAddresses...)
	stub.messageV2.BccAddresses = append([]string(nil), message.BccAddresses...)
	if stub.dispatch != nil {
		return stub.dispatch(ctx)
	}
	return stub.result, stub.err
}

func (stub *mailProviderStub) EffectTerminationBound() time.Duration {
	if stub.bound == 0 {
		return time.Second
	}
	return stub.bound
}

func (stub *mailProviderStub) Dispatch(
	ctx context.Context,
	token string,
	message connector_mail.Message,
) (connector_mail.DispatchResult, error) {
	stub.calls++
	stub.token = token
	stub.message = message
	stub.message.ToAddresses = append([]string(nil), message.ToAddresses...)
	if stub.dispatch != nil {
		return stub.dispatch(ctx)
	}
	return stub.result, stub.err
}

type mailThrottlerStub struct {
	wait          time.Duration
	err           error
	releaseNil    bool
	calls         int
	legacyCalls   int
	releases      int
	credentialRef string
	credential    connector_throttle.ResolvedCredential
	endpointClass string
}

func (stub *mailThrottlerStub) Throttle(
	_ context.Context,
	credentialRef, endpointClass string,
) (time.Duration, func(), error) {
	stub.legacyCalls++
	stub.credentialRef, stub.endpointClass = credentialRef, endpointClass
	if stub.releaseNil {
		return stub.wait, nil, stub.err
	}
	return stub.wait, func() { stub.releases++ }, stub.err
}

func (stub *mailThrottlerStub) ThrottleResolved(
	_ context.Context,
	credential connector_throttle.ResolvedCredential,
	endpointClass string,
) (time.Duration, func(), error) {
	stub.calls++
	stub.credential, stub.endpointClass = credential, endpointClass
	if stub.releaseNil {
		return stub.wait, nil, stub.err
	}
	return stub.wait, func() { stub.releases++ }, stub.err
}

type mailAttachmentReaderStub struct {
	opened     outboundintentv1.OpenedMailAttachment
	err        error
	requests   []outboundintentv1.OpenMailAttachmentRequest
	references []outboundintentv1.MailAttachmentV1
}

func (s *mailAttachmentReaderStub) OpenOutboundMailAttachment(
	_ context.Context,
	request outboundintentv1.OpenMailAttachmentRequest,
	expected outboundintentv1.MailAttachmentV1,
) (outboundintentv1.OpenedMailAttachment, error) {
	s.requests = append(s.requests, request)
	s.references = append(s.references, expected)
	result := s.opened
	result.Data = append([]byte(nil), s.opened.Data...)
	return result, s.err
}

type mailEffectFixture struct {
	effect    *mailEffect
	authority *mailAuthorityStub
	oauth     *mailOAuthStub
	providers *mailProviderResolverStub
	provider  *mailProviderStub
	throttler *mailThrottlerStub
}

func newMailEffectFixture() mailEffectFixture {
	oauth := &mailOAuthStub{
		principal: &connector_oauth.ConnectedPrincipal{
			ID: "principal-1", UserID: "user-1", ConnectorKey: "provider-a", Generation: 7,
			PrincipalAddress: "sender@example.com", State: connector_oauth.PrincipalConnected,
		},
		accessToken: "access-secret",
	}
	provider := &mailProviderStub{result: connector_mail.DispatchResult{
		RemoteMessageID: "remote-1", ResponseStatus: 200,
	}}
	providers := &mailProviderResolverStub{provider: provider, key: "provider-a"}
	throttler := &mailThrottlerStub{}
	authority := &mailAuthorityStub{binding: testResolvedMailBinding()}
	return mailEffectFixture{effect: &mailEffect{
		oauth: oauth, providers: providers, resolvedThrottler: throttler, resolvedAuthority: authority,
		connectorKey: "provider-a", credentialRef: "mail-oauth-client",
		credential: connector_throttle.ResolvedCredential{
			CanonicalID: "provider-a",
			Namespace:   "mail-owner",
		},
		endpointClass:  "mail.send",
		requiredScopes: []string{"mail.send"}, actorID: "user-1",
		message: connector_mail.Message{
			ToAddresses: []string{"recipient@example.com"}, Subject: "Hello", TextBody: "Body",
		},
	}, authority: authority, oauth: oauth, providers: providers, provider: provider, throttler: throttler}
}

type mailCheckpointCapture struct {
	calls    int
	status   int
	terminal TypedTerminalResult
	err      error
}

func dispatchMailEffect(t *testing.T, effect PreparedEffect, ctx context.Context, capture *mailCheckpointCapture) EffectResult {
	t.Helper()
	typed, ok := effect.(TypedTerminalResultEffect)
	if !ok {
		t.Fatal("mail effect does not implement TypedTerminalResultEffect")
	}
	return typed.DispatchWithTypedTerminalResultCheckpoint(ctx, func(_ context.Context, status int, terminal TypedTerminalResult) error {
		capture.calls++
		capture.status = status
		capture.terminal = terminal
		capture.terminal.CanonicalResult = append([]byte(nil), terminal.CanonicalResult...)
		return capture.err
	})
}

func TestMailPreparerDefersSecretsAndProviderIOUntilTypedDispatch(t *testing.T) {
	fixture := newMailEffectFixture()
	authority := &mailAuthorityStub{binding: testResolvedMailBinding()}
	providers := &mailProviderResolverStub{provider: fixture.provider, key: "provider-a"}
	preparer, err := NewResolvedMailPreparer(authority, fixture.oauth, providers, fixture.throttler)
	if err != nil {
		t.Fatal(err)
	}
	prepared, err := preparer.Prepare(t.Context(), testMailCommand(t))
	if err != nil {
		t.Fatal(err)
	}
	if authority.calls != 1 || providers.calls != 0 || fixture.oauth.principalCalls != 0 ||
		fixture.oauth.tokenCalls != 0 || fixture.throttler.calls != 0 || fixture.provider.calls != 0 {
		t.Fatalf("Prepare effects authority=%d resolver=%d principal=%d token=%d throttle=%d provider=%d",
			authority.calls, providers.calls, fixture.oauth.principalCalls, fixture.oauth.tokenCalls,
			fixture.throttler.calls, fixture.provider.calls)
	}
	if result := prepared.Dispatch(t.Context()); result.Outcome != EffectUncertain || fixture.provider.calls != 0 {
		t.Fatalf("unowned Dispatch = %#v; provider calls=%d", result, fixture.provider.calls)
	}

	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	capture := &mailCheckpointCapture{}
	result := dispatchMailEffect(t, prepared, ctx, capture)
	decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
	if decodeErr != nil || result.Outcome != EffectTypedTerminal || capture.calls != 1 || capture.status != 200 ||
		capture.terminal.ResultFormatVersion != outboundintentv1.MailResultFormatV1 ||
		decoded.Outcome != outboundintentv1.MailOutcomeDelivered || decoded.RemoteMessageID != "remote-1" ||
		decoded.SenderAddress != "sender@example.com" {
		t.Fatalf("mail dispatch result=%#v checkpoint=%#v decoded=%#v error=%v", result, capture, decoded, decodeErr)
	}
	if fixture.provider.calls != 1 || fixture.provider.token != "access-secret" ||
		fixture.provider.message.FromAddress != "sender@example.com" || fixture.provider.message.Subject != "Hello" ||
		!reflect.DeepEqual(fixture.provider.message.ToAddresses, []string{"recipient@example.com"}) {
		t.Fatalf("provider call count=%d token=%q message=%#v", fixture.provider.calls, fixture.provider.token, fixture.provider.message)
	}
	if providers.calls != 1 {
		t.Fatalf("provider resolver calls = %d", providers.calls)
	}
	if fixture.oauth.lastRequest.ExpectedGeneration != 7 ||
		!reflect.DeepEqual(fixture.oauth.lastRequest.RequiredScopes, []string{"mail.send"}) ||
		authority.revalidateCalls != 1 ||
		!reflect.DeepEqual(authority.revalidated, ResolvedMailBinding{
			MailBinding: testResolvedMailBinding(), Credential: testResolvedMailCredential(),
		}) ||
		fixture.throttler.calls != 1 || fixture.throttler.releases != 1 ||
		fixture.throttler.credential.CanonicalID != "provider-a" ||
		fixture.throttler.credential.Namespace != "mail-owner" ||
		fixture.throttler.endpointClass != "mail.send" {
		t.Fatalf("OAuth request=%#v throttle=%#v", fixture.oauth.lastRequest, fixture.throttler)
	}
}

func TestMailPreparerV2DispatchesRecipientClassesAndHTMLThroughVersionedProvider(t *testing.T) {
	fixture := newMailEffectFixture()
	authority := &mailAuthorityStub{binding: testResolvedMailBinding()}
	providers := &mailProviderResolverStub{provider: fixture.provider, key: "provider-a"}
	preparer, err := NewResolvedMailPreparer(authority, fixture.oauth, providers, fixture.throttler)
	if err != nil {
		t.Fatal(err)
	}
	prepared, err := preparer.Prepare(t.Context(), testMailV2Command(t))
	if err != nil {
		t.Fatal(err)
	}
	capture := &mailCheckpointCapture{}
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	if result := dispatchMailEffect(t, prepared, ctx, capture); result.Outcome != EffectTypedTerminal {
		t.Fatalf("mail v2 result = %#v", result)
	}
	message := fixture.provider.messageV2
	if message.Message.FromAddress != "sender@example.com" ||
		!reflect.DeepEqual(message.Message.ToAddresses, []string{"recipient@example.com"}) ||
		!reflect.DeepEqual(message.CcAddresses, []string{"cc@example.com"}) ||
		!reflect.DeepEqual(message.BccAddresses, []string{"bcc@example.com"}) ||
		message.HTMLBody != "<p>Body</p>" {
		t.Fatalf("mail v2 provider message = %#v", message)
	}
}

func TestMailPreparerV3LoadsEveryAttachmentBeforeOneProviderDispatch(t *testing.T) {
	fixture := newMailEffectFixture()
	content := []byte("intact-pdf")
	sum := sha256.Sum256(content)
	reader := &mailAttachmentReaderStub{opened: outboundintentv1.OpenedMailAttachment{
		FileName: "営業資料.pdf", ContentType: "application/pdf", ContentLength: uint64(len(content)),
		ContentDigest: "sha256:" + hex.EncodeToString(sum[:]), Data: content,
		Receipts: validOutboundReceipts(41),
	}}
	preparer, err := NewResolvedMailPreparerWithAttachments(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
		fixture.providers, fixture.throttler, reader,
	)
	if err != nil {
		t.Fatal(err)
	}
	prepared, err := preparer.Prepare(t.Context(), testMailV3Command(t, content))
	if err != nil {
		t.Fatal(err)
	}
	capture := &mailCheckpointCapture{}
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	if result := dispatchMailEffect(t, prepared, ctx, capture); result.Outcome != EffectTypedTerminal {
		t.Fatalf("result = %#v", result)
	}
	if len(reader.requests) != 1 || reader.requests[0].IntentID != "intent-mail-1" ||
		reader.requests[0].IntegrationCommandID != "command-mail-1" ||
		len(reader.references) != 1 || reader.references[0].FileName != "営業資料.pdf" ||
		len(fixture.provider.messageV3.Attachments) != 1 ||
		!reflect.DeepEqual(fixture.provider.messageV3.Attachments[0].Data, content) || fixture.provider.calls != 1 {
		t.Fatalf("requests=%#v provider=%#v", reader.requests, fixture.provider)
	}
}

func TestMailPreparerV3TransientAttachmentFailureDoesNotDispatchOrCheckpoint(t *testing.T) {
	tests := []struct {
		name string
		err  error
	}{
		{name: "canceled", err: context.Canceled},
		{name: "runtime unavailable", err: &commonv1.Failure{Category: commonv1.FailureUnavailable}},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			fixture := newMailEffectFixture()
			reader := &mailAttachmentReaderStub{err: test.err}
			preparer, err := NewResolvedMailPreparerWithAttachments(
				&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
				fixture.providers, fixture.throttler, reader,
			)
			if err != nil {
				t.Fatal(err)
			}
			prepared, err := preparer.Prepare(t.Context(), testMailV3Command(t, []byte("intact-pdf")))
			if err != nil {
				t.Fatal(err)
			}
			capture := &mailCheckpointCapture{}
			result := dispatchMailEffect(t, prepared, t.Context(), capture)
			if result.Outcome != EffectNotDispatched || result.FailureCode != "mail_attachment_unavailable" ||
				capture.calls != 0 || fixture.provider.calls != 0 || fixture.oauth.principalCalls != 0 ||
				fixture.throttler.calls != 0 {
				t.Fatalf("result=%#v checkpoint=%#v provider=%d oauth=%d throttle=%d",
					result, capture, fixture.provider.calls, fixture.oauth.principalCalls, fixture.throttler.calls)
			}
		})
	}
}

func TestMailPreparerV3PermanentRuntimeAttachmentFailureCheckpointsRejection(t *testing.T) {
	categories := []commonv1.FailureCategory{
		commonv1.FailureInvalidRequest,
		commonv1.FailureUnauthenticated,
		commonv1.FailureForbidden,
		commonv1.FailureMissing,
		commonv1.FailureConflict,
		commonv1.FailureIncompatible,
	}
	for _, category := range categories {
		t.Run(string(category), func(t *testing.T) {
			fixture := newMailEffectFixture()
			reader := &mailAttachmentReaderStub{err: &commonv1.Failure{Category: category}}
			preparer, err := NewResolvedMailPreparerWithAttachments(
				&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
				fixture.providers, fixture.throttler, reader,
			)
			if err != nil {
				t.Fatal(err)
			}
			prepared, err := preparer.Prepare(t.Context(), testMailV3Command(t, []byte("intact-pdf")))
			if err != nil {
				t.Fatal(err)
			}
			capture := &mailCheckpointCapture{}
			ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
			defer cancel()
			result := dispatchMailEffect(t, prepared, ctx, capture)
			decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
			if decodeErr != nil || result.Outcome != EffectTypedTerminal || capture.calls != 1 ||
				decoded.FailureCode != outboundintentv1.MailFailureAttachmentUnavailable ||
				fixture.provider.calls != 0 || fixture.oauth.principalCalls != 0 || fixture.throttler.calls != 0 {
				t.Fatalf("result=%#v checkpoint=%#v decoded=%#v err=%v provider=%d oauth=%d throttle=%d",
					result, capture, decoded, decodeErr, fixture.provider.calls, fixture.oauth.principalCalls, fixture.throttler.calls)
			}
		})
	}
}

func TestMailPreparerV3AttachmentIdentityMismatchCheckpointsRejection(t *testing.T) {
	fixture := newMailEffectFixture()
	reader := &mailAttachmentReaderStub{opened: outboundintentv1.OpenedMailAttachment{
		FileName: "wrong.pdf", ContentType: "application/pdf", Data: []byte("intact-pdf"),
	}}
	preparer, err := NewResolvedMailPreparerWithAttachments(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
		fixture.providers, fixture.throttler, reader,
	)
	if err != nil {
		t.Fatal(err)
	}
	prepared, err := preparer.Prepare(t.Context(), testMailV3Command(t, []byte("intact-pdf")))
	if err != nil {
		t.Fatal(err)
	}
	capture := &mailCheckpointCapture{}
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	result := dispatchMailEffect(t, prepared, ctx, capture)
	decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
	if decodeErr != nil || result.Outcome != EffectTypedTerminal || capture.calls != 1 ||
		decoded.FailureCode != outboundintentv1.MailFailureAttachmentUnavailable ||
		fixture.provider.calls != 0 || fixture.oauth.principalCalls != 0 || fixture.throttler.calls != 0 {
		t.Fatalf("result=%#v checkpoint=%#v decoded=%#v err=%v provider=%d oauth=%d throttle=%d",
			result, capture, decoded, decodeErr, fixture.provider.calls, fixture.oauth.principalCalls, fixture.throttler.calls)
	}
}

func TestMailPreparerV3IncompleteAttachmentReceiptsCheckpointRejection(t *testing.T) {
	fixture := newMailEffectFixture()
	content := []byte("intact-pdf")
	sum := sha256.Sum256(content)
	reader := &mailAttachmentReaderStub{opened: outboundintentv1.OpenedMailAttachment{
		FileName: "営業資料.pdf", ContentType: "application/pdf",
		ContentLength: uint64(len(content)), ContentDigest: "sha256:" + hex.EncodeToString(sum[:]),
		Data: content, Receipts: outboundintentv1.Receipts{CatalogRevision: 41},
	}}
	preparer, err := NewResolvedMailPreparerWithAttachments(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
		fixture.providers, fixture.throttler, reader,
	)
	if err != nil {
		t.Fatal(err)
	}
	prepared, err := preparer.Prepare(t.Context(), testMailV3Command(t, content))
	if err != nil {
		t.Fatal(err)
	}
	capture := &mailCheckpointCapture{}
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	result := dispatchMailEffect(t, prepared, ctx, capture)
	decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
	if decodeErr != nil || result.Outcome != EffectTypedTerminal || capture.calls != 1 ||
		decoded.FailureCode != outboundintentv1.MailFailureAttachmentUnavailable ||
		fixture.provider.calls != 0 || fixture.oauth.principalCalls != 0 || fixture.throttler.calls != 0 {
		t.Fatalf("result=%#v checkpoint=%#v decoded=%#v err=%v provider=%d oauth=%d throttle=%d",
			result, capture, decoded, decodeErr, fixture.provider.calls, fixture.oauth.principalCalls, fixture.throttler.calls)
	}
}

func TestMailPreparerV2RejectsProviderWithoutVersionedDelivery(t *testing.T) {
	fixture := newMailEffectFixture()
	provider := &mailProviderV1OnlyStub{
		result: connector_mail.DispatchResult{RemoteMessageID: "must-not-dispatch", ResponseStatus: 200},
	}
	providers := &mailProviderResolverStub{provider: provider, key: "provider-a"}
	preparer, err := NewResolvedMailPreparer(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth, providers, fixture.throttler,
	)
	if err != nil {
		t.Fatal(err)
	}
	prepared, err := preparer.Prepare(t.Context(), testMailV2Command(t))
	if err != nil {
		t.Fatal(err)
	}
	capture := &mailCheckpointCapture{}
	// A deadline too short for the provider window and a broken OAuth principal
	// prove permanent incompatibility is classified before transient dispatch
	// preparation while still leaving time to persist the terminal checkpoint.
	fixture.oauth.principalErr = errors.New("must not resolve OAuth")
	ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond)
	defer cancel()
	result := dispatchMailEffect(t, prepared, ctx, capture)
	decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
	if decodeErr != nil || result.Outcome != EffectTypedTerminal || capture.calls != 1 ||
		decoded.Outcome != outboundintentv1.MailOutcomeRejected ||
		decoded.FailureCode != outboundintentv1.MailFailureProviderRejected || provider.calls != 0 ||
		fixture.oauth.principalCalls != 0 || fixture.throttler.calls != 0 {
		t.Fatalf("result=%#v checkpoint=%#v decoded=%#v error=%v provider=%#v oauth=%#v throttle=%#v",
			result, capture, decoded, decodeErr, provider, fixture.oauth, fixture.throttler)
	}
}

func TestMailEffectRevalidatesExactBindingBeforeOAuthToken(t *testing.T) {
	tests := []struct {
		name        string
		err         error
		wantEffect  EffectOutcome
		checkpoints int
	}{
		{
			name: "inactive exact generation",
			err: &PreparationError{
				Code: "mail_binding_unavailable", Cause: ErrIdentityConflict,
			},
			wantEffect: EffectTypedTerminal, checkpoints: 1,
		},
		{
			name: "transient authority failure",
			err: &PreparationError{
				Code: "mail_binding_unavailable", Retryable: true, Cause: errors.New("database unavailable"),
			},
			wantEffect: EffectNotDispatched,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			fixture := newMailEffectFixture()
			fixture.authority.revalidateErr = test.err
			ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
			defer cancel()
			capture := &mailCheckpointCapture{}
			result := dispatchMailEffect(t, fixture.effect, ctx, capture)
			if result.Outcome != test.wantEffect || capture.calls != test.checkpoints ||
				fixture.authority.revalidateCalls != 1 || fixture.throttler.calls != 1 ||
				fixture.throttler.releases != 1 || fixture.oauth.tokenCalls != 0 || fixture.provider.calls != 0 {
				t.Fatalf("result=%#v checkpoint=%#v authority=%#v throttle=%#v token=%d provider=%d",
					result, capture, fixture.authority, fixture.throttler, fixture.oauth.tokenCalls, fixture.provider.calls)
			}
			if test.checkpoints == 0 {
				return
			}
			decoded, err := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
			if err != nil || decoded.Outcome != outboundintentv1.MailOutcomeRejected ||
				decoded.FailureCode != outboundintentv1.MailFailureProviderRejected {
				t.Fatalf("terminal=%#v error=%v", decoded, err)
			}
		})
	}
}

func TestMailPreparerPreservesCredentialRefThrottleCompatibility(t *testing.T) {
	fixture := newMailEffectFixture()
	preparer, err := NewMailPreparer(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
		fixture.providers, fixture.throttler,
	)
	if err != nil {
		t.Fatal(err)
	}
	prepared, err := preparer.Prepare(t.Context(), testMailCommand(t))
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	result := dispatchMailEffect(t, prepared, ctx, &mailCheckpointCapture{})
	if result.Outcome != EffectTypedTerminal || fixture.throttler.legacyCalls != 1 ||
		fixture.throttler.calls != 0 || fixture.throttler.credentialRef != "mail-oauth-client" {
		t.Fatalf("legacy throttle result=%#v throttle=%#v", result, fixture.throttler)
	}
}

func TestWorkerPersistsMailResultBeforeRuntimeWriteback(t *testing.T) {
	fixture := newMailEffectFixture()
	preparer, err := NewResolvedMailPreparer(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
		&mailProviderResolverStub{provider: fixture.provider}, fixture.throttler,
	)
	if err != nil {
		t.Fatal(err)
	}
	command := testMailCommand(t)
	store := &memoryCommandStore{command: command}
	committer := &scriptedCommitter{}
	worker, err := NewWorker(store, preparer, committer, command.Scope, "worker-1")
	if err != nil {
		t.Fatal(err)
	}
	if processed, err := worker.ProcessOne(t.Context()); err != nil || !processed {
		t.Fatalf("provider pass = %v, %v", processed, err)
	}
	decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(store.typed.canonical)
	if decodeErr != nil || store.command.State != StateWriteback || fixture.provider.calls != 1 ||
		committer.calls != 0 || decoded.Outcome != outboundintentv1.MailOutcomeDelivered {
		t.Fatalf("durable checkpoint command=%#v provider=%d commits=%d result=%#v error=%v",
			store.command, fixture.provider.calls, committer.calls, decoded, decodeErr)
	}
	if processed, err := worker.ProcessOne(t.Context()); err != nil || !processed {
		t.Fatalf("writeback pass = %v, %v", processed, err)
	}
	if store.command.State != StateSucceeded || fixture.provider.calls != 1 || committer.calls != 1 ||
		committer.lastRequest.ResultDigest == "" || string(committer.lastRequest.CanonicalResult) != string(store.typed.canonical) {
		t.Fatalf("completed command=%#v provider=%d committer=%#v", store.command, fixture.provider.calls, committer)
	}
}

func TestMailEffectClassifiesProviderOutcomesWithoutBlindRetry(t *testing.T) {
	tests := []struct {
		name        string
		result      connector_mail.DispatchResult
		err         error
		wantEffect  EffectOutcome
		wantOutcome outboundintentv1.MailDeliveryOutcome
		wantFailure string
		checkpoints int
	}{
		{name: "rejected", result: connector_mail.DispatchResult{ResponseStatus: 400},
			err: &connector_mail.ProviderError{Kind: connector_mail.ProviderRejected}, wantEffect: EffectTypedTerminal,
			wantOutcome: outboundintentv1.MailOutcomeRejected, wantFailure: outboundintentv1.MailFailureProviderRejected, checkpoints: 1},
		{name: "authorization", result: connector_mail.DispatchResult{ResponseStatus: 403},
			err: &connector_mail.ProviderError{Kind: connector_mail.ProviderAuthorization}, wantEffect: EffectTypedTerminal,
			wantOutcome: outboundintentv1.MailOutcomeRejected, wantFailure: outboundintentv1.MailFailureScope, checkpoints: 1},
		{name: "uncertain", result: connector_mail.DispatchResult{ResponseStatus: 429},
			err: &connector_mail.ProviderError{Kind: connector_mail.ProviderUncertain}, wantEffect: EffectTypedTerminal,
			wantOutcome: outboundintentv1.MailOutcomeUnknown, wantFailure: outboundintentv1.MailFailureOutcomeUnknown, checkpoints: 1},
		{name: "untyped failure", err: errors.New("transport result unknown"), wantEffect: EffectTypedTerminal,
			wantOutcome: outboundintentv1.MailOutcomeUnknown, wantFailure: outboundintentv1.MailFailureOutcomeUnknown, checkpoints: 1},
		{name: "proven not dispatched", err: &connector_mail.ProviderError{Kind: connector_mail.ProviderNotDispatched},
			wantEffect: EffectNotDispatched},
		{name: "invalid success receipt", result: connector_mail.DispatchResult{RemoteMessageID: " invalid ", ResponseStatus: 200},
			wantEffect: EffectTypedTerminal, wantOutcome: outboundintentv1.MailOutcomeUnknown,
			wantFailure: outboundintentv1.MailFailureOutcomeUnknown, checkpoints: 1},
		{name: "non-2xx success receipt", result: connector_mail.DispatchResult{RemoteMessageID: "remote-1", ResponseStatus: 500},
			wantEffect: EffectTypedTerminal, wantOutcome: outboundintentv1.MailOutcomeUnknown,
			wantFailure: outboundintentv1.MailFailureOutcomeUnknown, checkpoints: 1},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			fixture := newMailEffectFixture()
			fixture.provider.result, fixture.provider.err = test.result, test.err
			ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
			defer cancel()
			capture := &mailCheckpointCapture{}
			result := dispatchMailEffect(t, fixture.effect, ctx, capture)
			if result.Outcome != test.wantEffect || fixture.provider.calls != 1 || capture.calls != test.checkpoints {
				t.Fatalf("effect=%#v provider=%d checkpoint=%#v", result, fixture.provider.calls, capture)
			}
			if test.checkpoints == 0 {
				return
			}
			decoded, err := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
			if err != nil || decoded.Outcome != test.wantOutcome || decoded.FailureCode != test.wantFailure {
				t.Fatalf("terminal=%#v error=%v", decoded, err)
			}
		})
	}
}

func TestMailEffectClassifiesFailuresBeforeProviderDispatch(t *testing.T) {
	tests := []struct {
		name        string
		mutate      func(mailEffectFixture)
		wantEffect  EffectOutcome
		wantOutcome outboundintentv1.MailDeliveryOutcome
		wantFailure string
		checkpoints int
	}{
		{name: "mailbox missing", mutate: func(f mailEffectFixture) { f.oauth.principalErr = connector_oauth.ErrPrincipalNotFound },
			wantEffect: EffectTypedTerminal, wantOutcome: outboundintentv1.MailOutcomeRejected,
			wantFailure: outboundintentv1.MailFailureNotConnected, checkpoints: 1},
		{name: "mailbox retired", mutate: func(f mailEffectFixture) { f.oauth.principal.State = connector_oauth.PrincipalDisconnected },
			wantEffect: EffectTypedTerminal, wantOutcome: outboundintentv1.MailOutcomeRejected,
			wantFailure: outboundintentv1.MailFailureRetired, checkpoints: 1},
		{name: "scope mismatch", mutate: func(f mailEffectFixture) { f.oauth.accessTokenErr = []error{connector_oauth.ErrScopeMismatch} },
			wantEffect: EffectTypedTerminal, wantOutcome: outboundintentv1.MailOutcomeRejected,
			wantFailure: outboundintentv1.MailFailureScope, checkpoints: 1},
		{name: "transient OAuth", mutate: func(f mailEffectFixture) { f.oauth.accessTokenErr = []error{connector_oauth.ErrRefreshInProgress} },
			wantEffect: EffectNotDispatched},
		{name: "encryption key unavailable", mutate: func(f mailEffectFixture) { f.oauth.accessTokenErr = []error{connector_oauth.ErrEncryptionKeyMissing} },
			wantEffect: EffectNotDispatched},
		{name: "credential unavailable", mutate: func(f mailEffectFixture) { f.throttler.err = connector_throttle.ErrCredentialNotAvailable },
			wantEffect: EffectTypedTerminal, wantOutcome: outboundintentv1.MailOutcomeRejected,
			wantFailure: outboundintentv1.MailFailureProviderRejected, checkpoints: 1},
		{name: "throttle dependency", mutate: func(f mailEffectFixture) { f.throttler.err = errors.New("throttle unavailable") },
			wantEffect: EffectNotDispatched},
		{name: "provider window", mutate: func(f mailEffectFixture) { f.provider.bound = time.Hour },
			wantEffect: EffectNotDispatched},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			fixture := newMailEffectFixture()
			test.mutate(fixture)
			ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
			defer cancel()
			capture := &mailCheckpointCapture{}
			result := dispatchMailEffect(t, fixture.effect, ctx, capture)
			if result.Outcome != test.wantEffect || fixture.provider.calls != 0 || capture.calls != test.checkpoints {
				t.Fatalf("effect=%#v provider=%d checkpoint=%#v", result, fixture.provider.calls, capture)
			}
			if test.checkpoints > 0 {
				decoded, err := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
				if err != nil || decoded.Outcome != test.wantOutcome || decoded.FailureCode != test.wantFailure {
					t.Fatalf("terminal=%#v error=%v", decoded, err)
				}
			}
		})
	}
}

func TestMailEffectFinishesStagedRefreshAndReleasesCanceledThrottle(t *testing.T) {
	fixture := newMailEffectFixture()
	fixture.oauth.accessTokenErr = []error{connector_oauth.ErrEffectResultStaged, nil}
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	capture := &mailCheckpointCapture{}
	result := dispatchMailEffect(t, fixture.effect, ctx, capture)
	cancel()
	if result.Outcome != EffectTypedTerminal || fixture.oauth.tokenCalls != 2 || fixture.provider.calls != 1 || capture.calls != 1 {
		t.Fatalf("staged refresh effect=%#v token=%d provider=%d checkpoint=%d",
			result, fixture.oauth.tokenCalls, fixture.provider.calls, capture.calls)
	}

	fixture = newMailEffectFixture()
	fixture.throttler.wait = time.Hour
	canceled, cancel := context.WithCancel(t.Context())
	cancel()
	capture = &mailCheckpointCapture{}
	result = dispatchMailEffect(t, fixture.effect, canceled, capture)
	if result.Outcome != EffectNotDispatched || fixture.throttler.releases != 1 ||
		fixture.oauth.tokenCalls != 0 || fixture.provider.calls != 0 || capture.calls != 0 {
		t.Fatalf("canceled throttle effect=%#v throttle=%#v token=%d provider=%d checkpoint=%d",
			result, fixture.throttler, fixture.oauth.tokenCalls, fixture.provider.calls, capture.calls)
	}
}

func TestMailEffectReservesOuterDeadlineForTerminalCheckpoint(t *testing.T) {
	fixture := newMailEffectFixture()
	fixture.provider.bound = 20 * time.Millisecond
	var providerDeadline time.Time
	fixture.provider.dispatch = func(ctx context.Context) (connector_mail.DispatchResult, error) {
		providerDeadline, _ = ctx.Deadline()
		<-ctx.Done()
		return connector_mail.DispatchResult{}, &connector_mail.ProviderError{Kind: connector_mail.ProviderUncertain}
	}
	ctx, cancel := context.WithTimeout(t.Context(), 1100*time.Millisecond)
	defer cancel()
	outerDeadline, _ := ctx.Deadline()
	capture := &mailCheckpointCapture{}
	result := dispatchMailEffect(t, fixture.effect, ctx, capture)
	decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
	if decodeErr != nil || result.Outcome != EffectTypedTerminal || ctx.Err() != nil || capture.calls != 1 ||
		decoded.Outcome != outboundintentv1.MailOutcomeUnknown || decoded.FailureCode != outboundintentv1.MailFailureOutcomeUnknown {
		t.Fatalf("deadline result=%#v context=%v checkpoint=%#v terminal=%#v error=%v",
			result, ctx.Err(), capture, decoded, decodeErr)
	}
	if providerDeadline.IsZero() || outerDeadline.Sub(providerDeadline) < fixture.provider.bound+mailCheckpointReserve {
		t.Fatalf("provider deadline=%s outer deadline=%s termination=%s", providerDeadline, outerDeadline, fixture.provider.bound)
	}
}

func TestMailEffectCheckpointsAmbiguousResultDuringWorkerCancellation(t *testing.T) {
	fixture := newMailEffectFixture()
	providerStarted := make(chan struct{})
	fixture.provider.dispatch = func(ctx context.Context) (connector_mail.DispatchResult, error) {
		close(providerStarted)
		<-ctx.Done()
		return connector_mail.DispatchResult{}, &connector_mail.ProviderError{Kind: connector_mail.ProviderUncertain}
	}
	typed := TypedTerminalResultEffect(fixture.effect)
	dispatchCtx, cancelDispatch := context.WithTimeout(t.Context(), 3*time.Second)
	type dispatchObservation struct {
		result        EffectResult
		checkpointErr error
		terminal      TypedTerminalResult
	}
	done := make(chan dispatchObservation, 1)
	go func() {
		observation := dispatchObservation{}
		observation.result = typed.DispatchWithTypedTerminalResultCheckpoint(
			dispatchCtx,
			func(ctx context.Context, _ int, terminal TypedTerminalResult) error {
				observation.checkpointErr = ctx.Err()
				observation.terminal = terminal
				observation.terminal.CanonicalResult = append([]byte(nil), terminal.CanonicalResult...)
				return nil
			},
		)
		done <- observation
	}()
	<-providerStarted
	cancelDispatch()
	select {
	case observation := <-done:
		decoded, err := outboundintentv1.DecodeMailDeliveryResultV1(observation.terminal.CanonicalResult)
		if err != nil || observation.result.Outcome != EffectTypedTerminal || observation.checkpointErr != nil ||
			decoded.Outcome != outboundintentv1.MailOutcomeUnknown || decoded.FailureCode != outboundintentv1.MailFailureOutcomeUnknown {
			t.Fatalf("canceled dispatch result=%#v checkpoint error=%v terminal=%#v decode error=%v",
				observation.result, observation.checkpointErr, decoded, err)
		}
	case <-time.After(time.Second):
		t.Fatal("mail effect did not reconcile after worker cancellation")
	}
}

func TestMailEffectStopsAtSingleCheckpointAfterProviderResult(t *testing.T) {
	fixture := newMailEffectFixture()
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	capture := &mailCheckpointCapture{err: errors.New("commit response lost")}
	result := dispatchMailEffect(t, fixture.effect, ctx, capture)
	if result.Outcome != EffectUncertain || result.FailureCode != "typed_result_checkpoint_failed" ||
		fixture.provider.calls != 1 || capture.calls != 1 {
		t.Fatalf("checkpoint failure effect=%#v provider=%d checkpoint=%#v", result, fixture.provider.calls, capture)
	}
}

func TestMailPreparerRejectsInvalidIntentAndBinding(t *testing.T) {
	fixture := newMailEffectFixture()
	command := testMailCommand(t)
	command.Intent.CanonicalPayload = []byte(`{}`)
	preparer, _ := NewResolvedMailPreparer(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth,
		&mailProviderResolverStub{provider: fixture.provider}, fixture.throttler,
	)
	if _, err := preparer.Prepare(t.Context(), command); preparationCode(err) != "invalid_mail_intent" {
		t.Fatalf("invalid intent error = %v", err)
	}

	command = testMailCommand(t)
	preparer, _ = NewResolvedMailPreparer(
		&mailAuthorityStub{binding: MailBinding{
			ConnectorKey:   " provider-a",
			CredentialRef:  "mail-oauth-client",
			EndpointClass:  "mail.send",
			RequiredScopes: []string{"mail.send"},
		}}, fixture.oauth, &mailProviderResolverStub{provider: fixture.provider}, fixture.throttler,
	)
	if _, err := preparer.Prepare(t.Context(), command); preparationCode(err) != "invalid_mail_binding" {
		t.Fatalf("invalid binding error = %v", err)
	}
	preparer, _ = NewResolvedMailPreparer(
		&mailAuthorityStub{binding: MailBinding{
			ConnectorKey:   "provider-a",
			CredentialRef:  "mail-oauth-client",
			EndpointClass:  "mail.send",
			RequiredScopes: []string{"mail send"},
		}}, fixture.oauth, &mailProviderResolverStub{provider: fixture.provider}, fixture.throttler,
	)
	if _, err := preparer.Prepare(t.Context(), command); preparationCode(err) != "invalid_mail_binding" {
		t.Fatalf("invalid scope error = %v", err)
	}
	preparer, _ = NewResolvedMailPreparer(
		&mailAuthorityStub{
			binding: MailBinding{
				ConnectorKey: "provider-a", CredentialRef: "mail-oauth-client",
				EndpointClass: "mail.send", RequiredScopes: []string{"mail.send"},
			},
			credential: &connector_throttle.ResolvedCredential{Namespace: "mail-owner"},
		}, fixture.oauth, &mailProviderResolverStub{provider: fixture.provider}, fixture.throttler,
	)
	if _, err := preparer.Prepare(t.Context(), command); preparationCode(err) != "invalid_mail_binding" {
		t.Fatalf("missing resolved credential identity error = %v", err)
	}

}

func TestMailEffectUsesPostCheckpointProviderRegistryState(t *testing.T) {
	fixture := newMailEffectFixture()
	oldProvider := fixture.provider
	preparer, _ := NewResolvedMailPreparer(
		&mailAuthorityStub{binding: testResolvedMailBinding()}, fixture.oauth, fixture.providers, fixture.throttler,
	)
	prepared, err := preparer.Prepare(t.Context(), testMailCommand(t))
	if err != nil {
		t.Fatal(err)
	}
	newProvider := &mailProviderStub{result: connector_mail.DispatchResult{RemoteMessageID: "remote-2", ResponseStatus: 200}}
	fixture.providers.provider = newProvider
	ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	capture := &mailCheckpointCapture{}
	result := dispatchMailEffect(t, prepared, ctx, capture)
	decoded, decodeErr := outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
	if decodeErr != nil || result.Outcome != EffectTypedTerminal || oldProvider.calls != 0 || newProvider.calls != 1 ||
		decoded.RemoteMessageID != "remote-2" {
		t.Fatalf("rotated provider result=%#v old=%d new=%d terminal=%#v error=%v",
			result, oldProvider.calls, newProvider.calls, decoded, decodeErr)
	}

	fixture = newMailEffectFixture()
	fixture.providers.provider = nil
	ctx, cancel = context.WithTimeout(t.Context(), 5*time.Second)
	defer cancel()
	capture = &mailCheckpointCapture{}
	result = dispatchMailEffect(t, fixture.effect, ctx, capture)
	decoded, decodeErr = outboundintentv1.DecodeMailDeliveryResultV1(capture.terminal.CanonicalResult)
	if decodeErr != nil || result.Outcome != EffectTypedTerminal || capture.calls != 1 ||
		fixture.oauth.principalCalls != 0 || fixture.provider.calls != 0 ||
		decoded.Outcome != outboundintentv1.MailOutcomeRejected || decoded.FailureCode != outboundintentv1.MailFailureProviderRejected {
		t.Fatalf("removed provider result=%#v checkpoint=%#v terminal=%#v error=%v",
			result, capture, decoded, decodeErr)
	}
}

func preparationCode(err error) string {
	var preparation *PreparationError
	if errors.As(err, &preparation) && preparation != nil {
		return preparation.Code
	}
	return ""
}

func testResolvedMailBinding() MailBinding {
	return MailBinding{
		ConnectorKey:   "provider-a",
		CredentialRef:  "mail-oauth-client",
		EndpointClass:  "mail.send",
		RequiredScopes: []string{"mail.send"},
	}
}

func testResolvedMailCredential() connector_throttle.ResolvedCredential {
	return connector_throttle.ResolvedCredential{
		CanonicalID: "provider-a",
		Namespace:   "mail-owner",
	}
}

func testMailCommand(t *testing.T) Command {
	t.Helper()
	payload := []byte(`{"version":1,"authorizedActorId":"user-1","toAddresses":["recipient@example.com"],"subject":"Hello","textBody":"Body"}`)
	digest := sha256.Sum256(payload)
	scope := Scope{
		OrgID: "org-1", AppInstallationID: "01900000-0000-7000-8000-000000000001",
		AuthorizedWorkloadID: "integration-1", CatalogRevision: 41,
	}
	command := Command{CommandID: "command-mail-1", Scope: scope, State: StatePending, Intent: outboundintentv1.Intent{
		IntentID: "intent-mail-1", IdempotencyKey: "request-1",
		PayloadFormatVersion: outboundintentv1.MailPayloadFormatV1,
		PayloadDigest:        "sha256:" + hex.EncodeToString(digest[:]),
		CatalogRevision:      scope.CatalogRevision, Operation: outboundintentv1.OperationSendMail,
		SourceObject: "Contact", SourceRecordID: "contact-1", ConnectorOwnerKey: "mail-owner",
		FlowDeveloperName: "Std_SendRecordEmail", FlowActionName: "sendRecordEmail",
		ActionSlotID: "send-record-email", DestinationSlotID: "connected-mailbox",
		Bindings: []outboundintentv1.BindingReference{{
			Kind: catalogsemantic.BindingConnectorCalloutProfile, OwnerPack: "mail-owner",
			PackInstallationID: "install-1",
			PackPayloadDigest:  "nexus-pack-sha256-v1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
			BusinessKey:        "mail-owner/delivery", DefinitionVersion: 1,
		}},
		CanonicalPayload: payload,
	}}
	command.LinkKey = outboundLinkKey(command.Scope, command.Intent)
	if err := outboundintentv1.ValidateIntent(command.Intent); err != nil {
		t.Fatalf("test mail intent: %v", err)
	}
	return command
}

func testMailV3Command(t *testing.T, content []byte) Command {
	t.Helper()
	command := testMailCommand(t)
	sum := sha256.Sum256(content)
	payload, err := json.Marshal(outboundintentv1.MailDeliveryV3{
		Version: outboundintentv1.MailPayloadFormatV3, AuthorizedActorID: "user-1",
		ToAddresses: []string{"recipient@example.com"}, Subject: "Hello", TextBody: "Body",
		Attachments: []outboundintentv1.MailAttachmentV1{{
			FileID: "file-1", FileName: "営業資料.pdf", ContentType: "application/pdf",
			ContentLength: uint64(len(content)), ContentDigest: "sha256:" + hex.EncodeToString(sum[:]),
		}},
	})
	if err != nil {
		t.Fatal(err)
	}
	digest := sha256.Sum256(payload)
	command.Intent.PayloadFormatVersion = outboundintentv1.MailPayloadFormatV3
	command.Intent.PayloadDigest = "sha256:" + hex.EncodeToString(digest[:])
	command.Intent.CanonicalPayload = payload
	command.LinkKey = outboundLinkKey(command.Scope, command.Intent)
	if _, err := outboundintentv1.DecodeMailDeliveryV3(command.Intent); err != nil {
		t.Fatalf("test mail v3 intent: %v", err)
	}
	return command
}

func testMailV2Command(t *testing.T) Command {
	t.Helper()
	command := testMailCommand(t)
	payload, err := json.Marshal(outboundintentv1.MailDeliveryV2{
		Version: outboundintentv1.MailPayloadFormatV2, AuthorizedActorID: "user-1",
		ToAddresses: []string{"recipient@example.com"}, CcAddresses: []string{"cc@example.com"},
		BccAddresses: []string{"bcc@example.com"}, Subject: "Hello", TextBody: "Body", HTMLBody: "<p>Body</p>",
	})
	if err != nil {
		t.Fatal(err)
	}
	digest := sha256.Sum256(payload)
	command.Intent.PayloadFormatVersion = outboundintentv1.MailPayloadFormatV2
	command.Intent.PayloadDigest = "sha256:" + hex.EncodeToString(digest[:])
	command.Intent.CanonicalPayload = payload
	command.LinkKey = outboundLinkKey(command.Scope, command.Intent)
	if err := outboundintentv1.ValidateIntent(command.Intent); err != nil {
		t.Fatalf("test mail v2 intent: %v", err)
	}
	return command
}
