package outboundintentv1

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"testing"
)

func TestOpenMailAttachmentRequestBindsAcceptedV3IntentAndIndex(t *testing.T) {
	content := []byte("attachment")
	ref := testMailAttachmentReference(content)
	intent := testMailAttachmentIntent(t, ref)
	request := OpenMailAttachmentRequest{
		IntentID: intent.IntentID, PayloadDigest: intent.PayloadDigest,
		CatalogRevision: intent.CatalogRevision, IntegrationCommandID: "command-1",
		AttachmentIndex: 0,
	}
	got, err := ValidateOpenMailAttachmentRequestForAcceptedIntent(intent, "command-1", request)
	if err != nil || got != ref {
		t.Fatalf("attachment = %#v, err = %v", got, err)
	}

	wrongCommand := request
	wrongCommand.IntegrationCommandID = "command-2"
	if _, err := ValidateOpenMailAttachmentRequestForAcceptedIntent(intent, "command-1", wrongCommand); err == nil {
		t.Fatal("accepted a different Integration command")
	}
	outOfRange := request
	outOfRange.AttachmentIndex = 1
	if _, err := ValidateOpenMailAttachmentRequestForAcceptedIntent(intent, "command-1", outOfRange); err == nil {
		t.Fatal("accepted an attachment index absent from the frozen payload")
	}
	legacy := intent
	legacy.PayloadFormatVersion = MailPayloadFormatV2
	if _, err := ValidateOpenMailAttachmentRequestForAcceptedIntent(legacy, "command-1", request); err == nil {
		t.Fatal("accepted a non-V3 mail intent")
	}
}

func TestMailAttachmentStreamVerifierUsesFrozenReference(t *testing.T) {
	expectedData := []byte("attachment")
	expected := testMailAttachmentReference(expectedData)
	forgedData := []byte("substitute")
	forged := testMailAttachmentReference(forgedData)
	state, err := NewMailAttachmentStreamState(expected)
	if err != nil {
		t.Fatal(err)
	}
	if err := state.Observe(MailAttachmentChunk{
		FileName: forged.FileName, ContentType: forged.ContentType,
		ContentLength: forged.ContentLength, ContentDigest: forged.ContentDigest,
		Data: forgedData, Receipts: receiptsPtr(testReceipts(41)),
	}); err == nil {
		t.Fatal("accepted a self-consistent stream that differs from the frozen reference")
	}

	state, err = NewMailAttachmentStreamState(expected)
	if err != nil {
		t.Fatal(err)
	}
	if err := state.Observe(MailAttachmentChunk{
		FileName: expected.FileName, ContentType: expected.ContentType,
		ContentLength: expected.ContentLength, ContentDigest: expected.ContentDigest,
		Data: expectedData[:4], Receipts: receiptsPtr(testReceipts(41)),
	}); err != nil {
		t.Fatal(err)
	}
	if err := state.Observe(MailAttachmentChunk{}); err == nil {
		t.Fatal("accepted a zero-byte continuation frame")
	}
	if err := state.Observe(MailAttachmentChunk{Data: expectedData[4:]}); err != nil {
		t.Fatal(err)
	}
	if err := state.Complete(); err != nil {
		t.Fatal(err)
	}
}

func TestOpenedMailAttachmentRequiresCompleteRuntimeReceipts(t *testing.T) {
	data := []byte("attachment")
	expected := testMailAttachmentReference(data)
	request := OpenMailAttachmentRequest{
		IntentID: "intent", PayloadDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
		CatalogRevision: 41, IntegrationCommandID: "command-1",
	}
	opened := OpenedMailAttachment{
		FileName: expected.FileName, ContentType: expected.ContentType,
		ContentLength: expected.ContentLength, ContentDigest: expected.ContentDigest,
		Data: append([]byte(nil), data...), Receipts: testReceipts(41),
	}
	if err := ValidateOpenedMailAttachment(request, expected, opened); err != nil {
		t.Fatal(err)
	}
	opened.Receipts.SecurityEpoch = 0
	if err := ValidateOpenedMailAttachment(request, expected, opened); err == nil {
		t.Fatal("accepted incomplete Runtime receipts")
	}
}

func testMailAttachmentReference(data []byte) MailAttachmentV1 {
	sum := sha256.Sum256(data)
	return MailAttachmentV1{
		FileID: "file-1", FileName: "attachment.pdf", ContentType: "application/pdf",
		ContentLength: uint64(len(data)), ContentDigest: "sha256:" + hex.EncodeToString(sum[:]),
	}
}

func testMailAttachmentIntent(t *testing.T, attachment MailAttachmentV1) Intent {
	t.Helper()
	payload, err := json.Marshal(MailDeliveryV3{
		Version: MailPayloadFormatV3, AuthorizedActorID: "user-1",
		ToAddresses: []string{"recipient@example.com"}, Subject: "subject", TextBody: "body",
		Attachments: []MailAttachmentV1{attachment},
	})
	if err != nil {
		t.Fatal(err)
	}
	intent := testIntent(payload, 41)
	intent.Operation = OperationSendMail
	intent.PayloadFormatVersion = MailPayloadFormatV3
	return intent
}

func receiptsPtr(receipts Receipts) *Receipts { return &receipts }
