package catalogsnapshot

import (
	"bytes"
	"errors"
	"testing"
)

func TestServingCompatibilityReceiptIsCanonicalAndTamperEvident(t *testing.T) {
	input := ServingCompatibilityReceiptInput{
		OrgID: "org-1", AppInstallationID: "installation-1",
		AppReleaseDigest:     ComputeDigest([]byte("app-release")),
		AppCapabilityID:      "io.nexuscrm.sfcrm.ui.record-access.v1",
		RuntimeCapabilityID:  "runtime.records.evaluate-access.v1",
		WorkloadID:           "sfcrm-app-service",
		ServiceReleaseDigest: ComputeDigest([]byte("service-release")), OwnershipEpoch: 3,
		MetadataSourceDigest:      ComputeDigest([]byte("metadata-source")),
		CatalogSnapshotDigest:     ComputeDigest([]byte("catalog-snapshot")),
		RuntimeMetadataViewDigest: ComputeDigest([]byte("runtime-view")),
		Lineage:                   Lineage{CatalogRevision: 7, SchemaGeneration: 8, EngineGeneration: 9}, SecurityEpoch: 11,
	}
	first, err := BuildServingCompatibilityReceipt(input)
	if err != nil {
		t.Fatal(err)
	}
	second, err := BuildServingCompatibilityReceipt(input)
	if err != nil {
		t.Fatal(err)
	}
	if first.Digest() != second.Digest() || !bytes.Equal(first.Bytes(), second.Bytes()) {
		t.Fatal("identical receipt facts did not produce byte-identical content-addressed artifacts")
	}
	receipt, err := OpenServingCompatibilityReceipt(first)
	if err != nil {
		t.Fatal(err)
	}
	store, err := NewDirectoryStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	if err := store.Put(t.Context(), first); err != nil {
		t.Fatal(err)
	}
	stored, err := store.Get(t.Context(), ArtifactServingCompatibilityReceipt, first.Digest())
	if err != nil || stored.Digest() != first.Digest() || !bytes.Equal(stored.Bytes(), first.Bytes()) {
		t.Fatalf("stored receipt = %#v, %v", stored, err)
	}
	if receipt.Digest() != first.Digest() || receipt.CatalogSnapshotDigest() != input.CatalogSnapshotDigest ||
		receipt.RuntimeMetadataViewDigest() != input.RuntimeMetadataViewDigest ||
		receipt.MetadataSourceDigest() != input.MetadataSourceDigest || receipt.Lineage() != input.Lineage {
		t.Fatalf("opened receipt lost an owned fact: %#v", receipt)
	}

	tampered := first.Bytes()
	tampered[len(tampered)-2] ^= 1
	if _, err := OpenServingCompatibilityReceipt(artifactFromBytes(ArtifactServingCompatibilityReceipt, first.Digest(), tampered)); !errors.Is(err, ErrArtifactCorrupt) {
		t.Fatalf("tampered receipt error = %v", err)
	}

	withUnknown := append(append([]byte(nil), first.Bytes()[:len(first.Bytes())-1]...), []byte(`,"unexpected":true}`)...)
	if _, err := OpenServingCompatibilityReceipt(makeArtifact(ArtifactServingCompatibilityReceipt, withUnknown)); !errors.Is(err, ErrArtifactCorrupt) {
		t.Fatalf("unknown receipt fact error = %v", err)
	}
}

func TestServingCompatibilityReceiptRejectsIncompleteOrConflatedOwnership(t *testing.T) {
	valid := ServingCompatibilityReceiptInput{
		OrgID: "org-1", AppInstallationID: "installation-1",
		AppReleaseDigest: ComputeDigest([]byte("app-release")), AppCapabilityID: "app-capability",
		RuntimeCapabilityID: "runtime-capability", WorkloadID: "workload-1",
		ServiceReleaseDigest: ComputeDigest([]byte("service-release")), OwnershipEpoch: 1,
		CatalogSnapshotDigest: ComputeDigest([]byte("catalog")), RuntimeMetadataViewDigest: ComputeDigest([]byte("view")),
		Lineage: Lineage{CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1}, SecurityEpoch: 1,
	}
	tests := []struct {
		name   string
		mutate func(*ServingCompatibilityReceiptInput)
	}{
		{name: "missing workload", mutate: func(input *ServingCompatibilityReceiptInput) { input.WorkloadID = "" }},
		{name: "conflated capability", mutate: func(input *ServingCompatibilityReceiptInput) { input.AppCapabilityID = input.RuntimeCapabilityID }},
		{name: "malformed release digest", mutate: func(input *ServingCompatibilityReceiptInput) { input.ServiceReleaseDigest = "sha256:not-a-digest" }},
		{name: "malformed metadata source digest", mutate: func(input *ServingCompatibilityReceiptInput) { input.MetadataSourceDigest = "sha256:not-a-digest" }},
		{name: "missing ownership epoch", mutate: func(input *ServingCompatibilityReceiptInput) { input.OwnershipEpoch = 0 }},
		{name: "missing security epoch", mutate: func(input *ServingCompatibilityReceiptInput) { input.SecurityEpoch = 0 }},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			input := valid
			test.mutate(&input)
			if _, err := BuildServingCompatibilityReceipt(input); err == nil {
				t.Fatal("invalid ownership receipt was accepted")
			}
		})
	}
}

func TestServingCompatibilityReceiptV1RemainsReadable(t *testing.T) {
	input := ServingCompatibilityReceiptInput{
		OrgID: "org-1", AppInstallationID: "installation-1",
		AppReleaseDigest: ComputeDigest([]byte("app-release")), AppCapabilityID: "app-capability",
		RuntimeCapabilityID: "runtime-capability", WorkloadID: "workload-1",
		ServiceReleaseDigest: ComputeDigest([]byte("service-release")), OwnershipEpoch: 1,
		CatalogSnapshotDigest: ComputeDigest([]byte("catalog")), RuntimeMetadataViewDigest: ComputeDigest([]byte("view")),
		Lineage: Lineage{CatalogRevision: 1, SchemaGeneration: 1, EngineGeneration: 1}, SecurityEpoch: 1,
	}
	artifact, err := BuildServingCompatibilityReceipt(input)
	if err != nil {
		t.Fatal(err)
	}
	receipt, err := OpenServingCompatibilityReceipt(artifact)
	if err != nil {
		t.Fatal(err)
	}
	if receipt.MetadataSourceDigest() != "" {
		t.Fatalf("v1 metadata source digest = %q, want absent", receipt.MetadataSourceDigest())
	}
}
