package serving

import (
	"bytes"
	"errors"
	"fmt"
	"strings"
	"testing"
)

func TestNewReceiptComponentBuildsDetachedImmutableEvidence(t *testing.T) {
	input := receiptComponentTestInput(0)
	originalPlaintext := append([]byte(nil), input.PayloadPlaintext...)
	originalCiphertext := append([]byte(nil), input.PayloadCiphertext...)
	component, err := NewReceiptComponent(input)
	if err != nil {
		t.Fatalf("NewReceiptComponent(): %v", err)
	}
	input.PayloadPlaintext[0] ^= 0xff
	input.PayloadCiphertext[0] ^= 0xff

	if component.ComponentProtocolVersion() != ReceiptComponentProtocolV1 ||
		component.ID() != "receipt-component-0" ||
		component.ReceiptID() != "receipt-1" ||
		component.ComponentOrdinal() != 0 ||
		component.PayloadCiphertextProtocolVersion() != PayloadCiphertextProtocolV1 ||
		component.PayloadDigest() != prefixedIngressDigest(originalPlaintext) ||
		component.PayloadSizeBytes() != int64(len(originalPlaintext)) ||
		!bytes.Equal(component.PayloadCiphertext(), originalCiphertext) {
		t.Fatalf("ReceiptComponent projection = %#v", component)
	}
	returned := component.PayloadCiphertext()
	returned[0] ^= 0xff
	if !bytes.Equal(component.PayloadCiphertext(), originalCiphertext) {
		t.Fatal("ReceiptComponent ciphertext accessor aliases immutable evidence")
	}
}

func TestNewReceiptComponentRejectsEveryOpenOrUnboundedAxis(t *testing.T) {
	tests := []struct {
		name   string
		mutate func(*ReceiptComponentInput)
	}{
		{name: "unknown component protocol", mutate: func(v *ReceiptComponentInput) { v.ComponentProtocolVersion = "v2" }},
		{name: "unknown payload protocol", mutate: func(v *ReceiptComponentInput) { v.PayloadCiphertextProtocolVersion = "v2" }},
		{name: "invalid component ID", mutate: func(v *ReceiptComponentInput) { v.ID = " component" }},
		{name: "invalid receipt ID", mutate: func(v *ReceiptComponentInput) { v.ReceiptID = strings.Repeat("r", 256) }},
		{name: "negative ordinal", mutate: func(v *ReceiptComponentInput) { v.ComponentOrdinal = -1 }},
		{name: "ordinal at count bound", mutate: func(v *ReceiptComponentInput) { v.ComponentOrdinal = MaxReceiptComponentCount }},
		{name: "missing ciphertext", mutate: func(v *ReceiptComponentInput) { v.PayloadCiphertext = nil }},
		{name: "oversize ciphertext", mutate: func(v *ReceiptComponentInput) {
			v.PayloadCiphertext = make([]byte, MaxIngressPayloadCiphertextBytes+1)
		}},
		{name: "oversize plaintext", mutate: func(v *ReceiptComponentInput) {
			v.PayloadPlaintext = make([]byte, MaxIngressPayloadBytes+1)
		}},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			input := receiptComponentTestInput(0)
			test.mutate(&input)
			_, err := NewReceiptComponent(input)
			if !errors.Is(err, ErrInvalidIngressReceipt) &&
				!errors.Is(err, ErrUnsupportedIngressReceiptProtocol) {
				t.Fatalf("NewReceiptComponent() error = %v", err)
			}
		})
	}
}

func TestNewReceiptComponentAcceptsExactPersistenceBounds(t *testing.T) {
	input := receiptComponentTestInput(MaxReceiptComponentCount - 1)
	input.ID = strings.Repeat("c", connectorDigestProfileV2MaxIdentifierBytes)
	input.ReceiptID = strings.Repeat("r", connectorDigestProfileV2MaxIdentifierBytes)
	input.PayloadPlaintext = make([]byte, MaxIngressPayloadBytes)
	input.PayloadCiphertext = make([]byte, MaxIngressPayloadCiphertextBytes)
	component, err := NewReceiptComponent(input)
	if err != nil {
		t.Fatalf("NewReceiptComponent(exact bounds): %v", err)
	}
	if component.ComponentOrdinal() != MaxReceiptComponentCount-1 ||
		component.PayloadSizeBytes() != MaxIngressPayloadBytes ||
		len(component.PayloadCiphertext()) != MaxIngressPayloadCiphertextBytes {
		t.Fatalf("exact-bound component = %#v", component)
	}
}

func TestV2ReceiptValidatesCompleteOrderedReceiptComponentSet(t *testing.T) {
	admission := mustIngressReceiptTestAdmission(t)
	receipt := admission.receipt.copy()
	receipt.receiptComponents = []ReceiptComponent{
		mustReceiptComponent(t, 0),
		mustReceiptComponent(t, 1),
	}
	receipt.receiptComponentCount = int64(len(receipt.receiptComponents))
	admission.receipt = &receipt
	if err := admission.validateStored(); err != nil {
		t.Fatalf("validate complete ReceiptComponent graph: %v", err)
	}

	projected, ok := admission.V2Receipt()
	if !ok || projected.ReceiptComponentCount() != 2 || len(projected.ReceiptComponents()) != 2 {
		t.Fatalf("V2Receipt component projection = %#v found=%t", projected, ok)
	}
	components := projected.ReceiptComponents()
	components[0].payloadCiphertext[0] ^= 0xff
	components[1].id = "mutated"
	reloaded, _ := admission.V2Receipt()
	if !reloaded.receiptComponents[0].equal(receipt.receiptComponents[0]) ||
		!reloaded.receiptComponents[1].equal(receipt.receiptComponents[1]) {
		t.Fatal("V2Receipt component accessor aliases immutable graph")
	}

	mutations := []struct {
		name   string
		mutate func(*V2Receipt)
	}{
		{name: "count mismatch", mutate: func(v *V2Receipt) { v.receiptComponentCount++ }},
		{name: "count overflow", mutate: func(v *V2Receipt) { v.receiptComponentCount = MaxReceiptComponentCount + 1 }},
		{name: "wrong parent", mutate: func(v *V2Receipt) { v.receiptComponents[1].receiptID = "receipt-2" }},
		{name: "ordinal gap", mutate: func(v *V2Receipt) { v.receiptComponents[1].componentOrdinal = 2 }},
		{name: "duplicate ID", mutate: func(v *V2Receipt) { v.receiptComponents[1].id = v.receiptComponents[0].id }},
		{name: "unknown stored protocol", mutate: func(v *V2Receipt) { v.receiptComponents[0].componentProtocolVersion = "v2" }},
		{name: "invalid stored digest", mutate: func(v *V2Receipt) { v.receiptComponents[0].payloadDigest = "sha256:bad" }},
	}
	for _, mutation := range mutations {
		t.Run(mutation.name, func(t *testing.T) {
			corrupt := receipt.copy()
			mutation.mutate(&corrupt)
			if err := corrupt.validateStored(); !errors.Is(err, ErrStoredIngressReceiptInvalid) {
				t.Fatalf("validate corrupt component graph error = %v", err)
			}
		})
	}
}

func TestV2ReceiptBoundsAggregateReceiptComponentEvidence(t *testing.T) {
	receipt := mustIngressReceiptTestAdmission(t).receipt.copy()
	componentCount := int(MaxReceiptComponentSetBytes/uint64(MaxIngressPayloadBytes)) + 1
	receipt.receiptComponents = make([]ReceiptComponent, componentCount)
	for ordinal := range receipt.receiptComponents {
		component := mustReceiptComponent(t, int64(ordinal))
		component.payloadSizeBytes = MaxIngressPayloadBytes
		component.payloadCiphertext = []byte{byte(ordinal)}
		receipt.receiptComponents[ordinal] = component
	}
	receipt.receiptComponentCount = int64(componentCount)
	if err := receipt.validateStored(); !errors.Is(err, ErrStoredIngressReceiptInvalid) ||
		!strings.Contains(err.Error(), "plaintext set") {
		t.Fatalf("aggregate component bound error = %v", err)
	}

	if total, ok := addReceiptComponentSetBytes(MaxReceiptComponentSetBytes-1, 1); !ok || total != MaxReceiptComponentSetBytes {
		t.Fatalf("exact aggregate bound = %d, %t", total, ok)
	}
	if _, ok := addReceiptComponentSetBytes(MaxReceiptComponentSetBytes, 1); ok {
		t.Fatal("aggregate bound accepted one extra byte")
	}
	if _, ok := addReceiptComponentSetBytes(^uint64(0), 0); ok {
		t.Fatal("aggregate bound accepted an already-overflowed total")
	}
}

func TestNewV2TransportAdmissionBuildsClosedImmutableAuthority(t *testing.T) {
	input := ingressReceiptTestInput()
	originalCiphertext := append([]byte(nil), input.PayloadCiphertext...)
	originalPlaintext := append([]byte(nil), input.PayloadPlaintext...)
	admission, err := NewV2TransportAdmission(input)
	if err != nil {
		t.Fatalf("NewV2TransportAdmission(): %v", err)
	}
	input.PayloadCiphertext[0] ^= 0xff
	input.PayloadPlaintext[0] ^= 0xff

	receipt, ok := admission.V2Receipt()
	if !ok || string(receipt.PayloadCiphertext()) != string(originalCiphertext) {
		t.Fatalf("V2Receipt() did not preserve detached ciphertext: %#v found=%t", receipt, ok)
	}
	returned := receipt.PayloadCiphertext()
	returned[0] ^= 0xff
	reloaded, _ := admission.V2Receipt()
	if string(reloaded.PayloadCiphertext()) != string(originalCiphertext) {
		t.Fatal("V2Receipt ciphertext accessor aliases immutable authority")
	}
	if admission.Disposition() != ReceiptAdmissionDispositionV2Receipt ||
		admission.AdmissionKeyID() != input.AdmissionKeyID ||
		admission.AuthorizedPayloadDigest() != prefixedIngressDigest(originalPlaintext) ||
		receipt.PayloadDigest() != prefixedIngressDigest(originalPlaintext) ||
		receipt.PayloadSizeBytes() != int64(len(originalPlaintext)) ||
		admission.Identity().TransportIdempotencyKey() != input.TransportIdempotencyKey {
		t.Fatalf("admission projection is incomplete: %#v", admission)
	}
	if _, ok := admission.V1AcceptanceTombstone(); ok {
		t.Fatal("V2 admission exposed a V1 tombstone")
	}
}

func TestNewV2TransportAdmissionBoundsCiphertextBeforeImmutableCopy(t *testing.T) {
	ciphertext := make([]byte, MaxIngressPayloadCiphertextBytes+1)
	input := ingressReceiptTestInput()
	input.PayloadCiphertext = ciphertext[:MaxIngressPayloadCiphertextBytes]
	admission, err := NewV2TransportAdmission(input)
	if err != nil {
		t.Fatalf("NewV2TransportAdmission(exact ciphertext bound): %v", err)
	}
	receipt, ok := admission.V2Receipt()
	if !ok || len(receipt.PayloadCiphertext()) != MaxIngressPayloadCiphertextBytes {
		t.Fatalf("exact-bound ciphertext was not retained: found=%t bytes=%d", ok, len(receipt.PayloadCiphertext()))
	}

	input.PayloadCiphertext = ciphertext
	admission, err = NewV2TransportAdmission(input)
	if !errors.Is(err, ErrInvalidIngressReceipt) {
		t.Fatalf("NewV2TransportAdmission(oversize ciphertext) error = %v", err)
	}
	if _, ok := admission.V2Receipt(); ok {
		t.Fatal("oversize ciphertext constructed immutable receipt authority")
	}
}

func TestTransportIdentitySurvivesServingScopeAndEpochChanges(t *testing.T) {
	first := ingressReceiptTestInput()
	second := first
	second.ServingScopeDigest = strings.Repeat("d", 64)
	second.AccountServingGateID = "account-gate-2"
	second.AccountServingFenceToken = 42
	second.ServingEpochID = "serving-epoch-2"
	second.ServingEpochDigest = "sha256:" + strings.Repeat("e", 64)

	firstAdmission := mustNewIngressReceiptAdmission(t, first)
	secondAdmission := mustNewIngressReceiptAdmission(t, second)
	if firstAdmission.Identity() != secondAdmission.Identity() {
		t.Fatal("serving scope or epoch changed the transport idempotency identity")
	}
	if firstAdmission.ServingScopeDigest() == secondAdmission.ServingScopeDigest() ||
		firstAdmission.equal(secondAdmission) {
		t.Fatal("scope evidence was not retained outside the transport identity")
	}
}

func TestNewV2TransportAdmissionRejectsEveryOpenOrPartialAxis(t *testing.T) {
	tests := []struct {
		name   string
		mutate func(*V2TransportAdmissionInput)
	}{
		{name: "unknown receipt protocol", mutate: func(v *V2TransportAdmissionInput) { v.ReceiptProtocolVersion = "v2" }},
		{name: "unknown key protocol", mutate: func(v *V2TransportAdmissionInput) { v.AdmissionKeyProtocolVersion = "v2" }},
		{name: "unknown payload protocol", mutate: func(v *V2TransportAdmissionInput) { v.PayloadCiphertextProtocolVersion = "v2" }},
		{name: "invalid receipt ID", mutate: func(v *V2TransportAdmissionInput) { v.ReceiptID = " receipt" }},
		{name: "invalid key ID", mutate: func(v *V2TransportAdmissionInput) { v.AdmissionKeyID = strings.Repeat("x", 256) }},
		{name: "invalid account", mutate: func(v *V2TransportAdmissionInput) { v.ConnectorAccountID = "account\x00" }},
		{name: "unknown channel", mutate: func(v *V2TransportAdmissionInput) { v.IngressChannel = "email" }},
		{name: "zero fence", mutate: func(v *V2TransportAdmissionInput) { v.AccountServingFenceToken = 0 }},
		{name: "missing ciphertext", mutate: func(v *V2TransportAdmissionInput) { v.PayloadCiphertext = nil }},
		{name: "oversize ciphertext", mutate: func(v *V2TransportAdmissionInput) {
			v.PayloadCiphertext = make([]byte, MaxIngressPayloadCiphertextBytes+1)
		}},
		{name: "oversize plaintext", mutate: func(v *V2TransportAdmissionInput) {
			v.PayloadPlaintext = make([]byte, MaxIngressPayloadBytes+1)
		}},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			input := ingressReceiptTestInput()
			test.mutate(&input)
			_, err := NewV2TransportAdmission(input)
			if !errors.Is(err, ErrInvalidIngressReceipt) &&
				!errors.Is(err, ErrUnsupportedIngressReceiptProtocol) {
				t.Fatalf("NewV2TransportAdmission() error = %v", err)
			}
		})
	}
	componentGraph := mustIngressReceiptTestAdmission(t)
	componentGraph.receipt.receiptComponentCount = 1
	if err := componentGraph.validateStored(); !errors.Is(err, ErrStoredIngressReceiptInvalid) {
		t.Fatalf("nonzero component graph error = %v", err)
	}
}

func TestTransportAdmissionValidatesRetainedV1TombstoneWithoutCreatingReceipt(t *testing.T) {
	identity, err := NewTransportIdentity(
		"connector-account-1",
		IngressChannelWebhook,
		"delivery-1",
	)
	if err != nil {
		t.Fatalf("NewTransportIdentity(): %v", err)
	}
	reference := []byte{0x00, 0xff, 'v', '1'}
	protocol := PredecessorReferenceProtocolV1
	digest := prefixedIngressDigest(reference)
	admission := TransportAdmission{key: receiptAdmissionKey{
		id:                         "receipt-key-v1",
		protocolVersion:            ReceiptAdmissionKeyProtocolV1,
		servingScopeDigest:         strings.Repeat("a", 64),
		identity:                   identity,
		accountServingGateID:       "account-gate-1",
		authorizedPayloadDigest:    "sha256:" + strings.Repeat("c", 64),
		disposition:                ReceiptAdmissionDispositionV1Tombstone,
		predecessorProtocolVersion: &protocol,
		predecessorRef:             append([]byte(nil), reference...),
		predecessorRefDigest:       &digest,
	}}
	if err := admission.validateStored(); err != nil {
		t.Fatalf("validate retained V1 tombstone: %v", err)
	}
	if _, ok := admission.V2Receipt(); ok {
		t.Fatal("V1 tombstone materialized a V2Receipt")
	}
	tombstone, ok := admission.V1AcceptanceTombstone()
	if !ok || string(tombstone.Reference()) != string(reference) || tombstone.ReferenceDigest() != digest {
		t.Fatalf("V1AcceptanceTombstone() = %#v, %t", tombstone, ok)
	}
	mutated := tombstone.Reference()
	mutated[0] = 1
	again, _ := admission.V1AcceptanceTombstone()
	if string(again.Reference()) != string(reference) {
		t.Fatal("tombstone accessor aliases immutable reference bytes")
	}

	bad := admission.copy()
	bad.key.predecessorRefDigest = stringPointer("sha256:" + strings.Repeat("d", 64))
	if err := bad.validateStored(); !errors.Is(err, ErrStoredIngressReceiptInvalid) {
		t.Fatalf("corrupt predecessor digest error = %v", err)
	}
	bad = admission.copy()
	receipt := V2Receipt{}
	bad.receipt = &receipt
	if err := bad.validateStored(); !errors.Is(err, ErrStoredIngressReceiptInvalid) {
		t.Fatalf("mixed predecessor/receipt error = %v", err)
	}
}

func ingressReceiptTestInput() V2TransportAdmissionInput {
	return V2TransportAdmissionInput{
		ReceiptProtocolVersion:           ReceiptProtocolV1,
		AdmissionKeyProtocolVersion:      ReceiptAdmissionKeyProtocolV1,
		PayloadCiphertextProtocolVersion: PayloadCiphertextProtocolV1,
		ReceiptID:                        "receipt-1",
		AdmissionKeyID:                   "receipt-key-1",
		ServingScopeDigest:               strings.Repeat("a", 64),
		ConnectorAccountID:               "connector-account-1",
		AccountServingGateID:             "account-gate-1",
		AccountServingFenceToken:         41,
		ServingEpochID:                   "serving-epoch-1",
		ServingEpochDigest:               "sha256:" + strings.Repeat("b", 64),
		IngressChannel:                   IngressChannelWebhook,
		TransportIdempotencyKey:          "delivery-1",
		PayloadPlaintext:                 []byte("canonical plaintext payload"),
		PayloadCiphertext:                []byte{0x00, 0xff, 'c', 'i', 'p', 'h', 'e', 'r'},
	}
}

func receiptComponentTestInput(ordinal int64) ReceiptComponentInput {
	return ReceiptComponentInput{
		ComponentProtocolVersion:         ReceiptComponentProtocolV1,
		PayloadCiphertextProtocolVersion: PayloadCiphertextProtocolV1,
		ID:                               fmt.Sprintf("receipt-component-%d", ordinal),
		ReceiptID:                        "receipt-1",
		ComponentOrdinal:                 ordinal,
		PayloadPlaintext:                 []byte(fmt.Sprintf("component-%d", ordinal)),
		PayloadCiphertext:                []byte{byte(ordinal), 0xff, 'c'},
	}
}

func mustReceiptComponent(t *testing.T, ordinal int64) ReceiptComponent {
	t.Helper()
	component, err := NewReceiptComponent(receiptComponentTestInput(ordinal))
	if err != nil {
		t.Fatalf("NewReceiptComponent(%d): %v", ordinal, err)
	}
	return component
}

func mustIngressReceiptTestAdmission(t *testing.T) TransportAdmission {
	t.Helper()
	admission, err := NewV2TransportAdmission(ingressReceiptTestInput())
	if err != nil {
		t.Fatalf("NewV2TransportAdmission(): %v", err)
	}
	return admission
}

func mustNewIngressReceiptAdmission(t *testing.T, input V2TransportAdmissionInput) TransportAdmission {
	t.Helper()
	admission, err := NewV2TransportAdmission(input)
	if err != nil {
		t.Fatalf("NewV2TransportAdmission(): %v", err)
	}
	return admission
}
