package streamgate

import (
	"testing"
	"time"
)

// TestNormalizedConstructorsRejectEmptyPayload verifies that text, reasoning,
// and tool call fragment constructors reject empty payloads by running the
// assembled value through Validate.
func TestNormalizedConstructorsRejectEmptyPayload(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)

	// Empty text delta
	if _, err := NewTextDeltaEvent("ch", "", ts); err == nil {
		t.Fatal("NewTextDeltaEvent with empty text should return error")
	}

	// Empty reasoning delta
	if _, err := NewReasoningDeltaEvent("ch", "", ts); err == nil {
		t.Fatal("NewReasoningDeltaEvent with empty reasoning should return error")
	}

	// Empty tool call args
	if _, err := NewToolCallFragmentEvent("ch", "id", "name", "", ts); err == nil {
		t.Fatal("NewToolCallFragmentEvent with empty args should return error")
	}

	// Non-empty variants should succeed and pass Validate
	ev, err := NewTextDeltaEvent("ch", "hello", ts)
	if err != nil {
		t.Fatalf("NewTextDeltaEvent with non-empty text: %v", err)
	}
	if err := ev.Validate(); err != nil {
		t.Fatalf("valid text delta failed Validate: %v", err)
	}

	ev, err = NewReasoningDeltaEvent("ch", "thinking...", ts)
	if err != nil {
		t.Fatalf("NewReasoningDeltaEvent with non-empty reasoning: %v", err)
	}
	if err := ev.Validate(); err != nil {
		t.Fatalf("valid reasoning delta failed Validate: %v", err)
	}

	ev, err = NewToolCallFragmentEvent("ch", "id", "name", `{"key":"val"}`, ts)
	if err != nil {
		t.Fatalf("NewToolCallFragmentEvent with non-empty args: %v", err)
	}
	if err := ev.Validate(); err != nil {
		t.Fatalf("valid tool call fragment failed Validate: %v", err)
	}
}

// TestFilterValueValidationRejectsMalformedPrivateState verifies that directly
// assembled FilterOutcome, RecoveryDirective, FilterDecision, SanitizedEvidence,
// and RecoveryIntent values with opposite payloads, invalid tokens, or unknown
// enums are rejected by Validate.
func TestFilterValueValidationRejectsMalformedPrivateState(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)

	// --- FilterOutcome: evaluated with errorCode ---
	badEvaluated := FilterOutcome{
		kind:      FilterOutcomeKindEvaluated,
		decision:  buildValidDecision(t),
		errorCode: mustToken("err.code"),
	}
	if err := badEvaluated.Validate(); err == nil {
		t.Fatal("evaluated outcome with errorCode should be rejected")
	}

	// --- FilterOutcome: evaluation_error with decision ---
	badError := FilterOutcome{
		kind:      FilterOutcomeKindEvaluationError,
		decision:  buildValidDecision(t),
		errorCode: mustToken("err.code"),
	}
	if err := badError.Validate(); err == nil {
		t.Fatal("evaluation_error outcome with decision should be rejected")
	}

	// --- FilterOutcome: not_applicable with decision ---
	badNA := FilterOutcome{
		kind:     FilterOutcomeKindNotApplicableForEpoch,
		decision: buildValidDecision(t),
	}
	if err := badNA.Validate(); err == nil {
		t.Fatal("not_applicable outcome with decision should be rejected")
	}

	// --- FilterOutcome: deferred with errorCode ---
	badDeferred := FilterOutcome{
		kind:      FilterOutcomeKindDeferredByRequirement,
		errorCode: mustToken("err.code"),
	}
	if err := badDeferred.Validate(); err == nil {
		t.Fatal("deferred outcome with errorCode should be rejected")
	}

	// --- FilterOutcome: unknown kind ---
	badKind := FilterOutcome{
		kind: FilterOutcomeKind("unknown"),
	}
	if err := badKind.Validate(); err == nil {
		t.Fatal("unknown outcome kind should be rejected")
	}

	// --- RecoveryDirective: exact with continuation fields ---
	badExact := RecoveryDirective{
		kind:        RecoveryDirectiveKindExact,
		requestRef:  mustToken("req.ref"),
		cursor:      5,
		snapshotRef: mustToken("snap.ref"),
	}
	if err := badExact.Validate(); err == nil {
		t.Fatal("exact directive with cursor/snapshotRef should be rejected")
	}

	// --- RecoveryDirective: continuation with exact fields ---
	badCont := RecoveryDirective{
		kind:        RecoveryDirectiveKindContinuation,
		requestRef:  mustToken("req.ref"),
		snapshotRef: mustToken("snap.ref"),
		cursor:      0,
	}
	if err := badCont.Validate(); err == nil {
		t.Fatal("continuation directive with requestRef should be rejected")
	}

	// --- RecoveryDirective: schema with exact fields ---
	badSchema := RecoveryDirective{
		kind:       RecoveryDirectiveKindSchema,
		requestRef: mustToken("req.ref"),
		schemaRef:  mustToken("schema.ref"),
		patchCode:  mustToken("patch.code"),
	}
	if err := badSchema.Validate(); err == nil {
		t.Fatal("schema directive with requestRef should be rejected")
	}

	// --- RecoveryDirective: unknown kind ---
	badDirKind := RecoveryDirective{
		kind: RecoveryDirectiveKind("unknown"),
	}
	if err := badDirKind.Validate(); err == nil {
		t.Fatal("unknown directive kind should be rejected")
	}

	// --- FilterDecision: invalid consumerID token ---
	badDecision := FilterDecision{
		kind:       FilterDecisionKindPass,
		consumerID: StableToken{value: "INVALID"},
		filterID:   mustToken("filter.id"),
		ruleID:     mustToken("rule.id"),
		evidence:   buildValidEvidence(t, ts),
	}
	if err := badDecision.Validate(); err == nil {
		t.Fatal("decision with invalid consumerID should be rejected")
	}

	// --- SanitizedEvidence: unknown event kind ---
	badEvidence := SanitizedEvidence{
		eventKind:      EventKind("unknown_kind"),
		channel:        "ch",
		filterRule:     mustToken("filter.rule"),
		outcome:        FilterOutcomeKindEvaluated,
		descriptorCode: mustToken("desc.code"),
		fingerprint:    [32]byte{1},
		count:          0,
		offset:         0,
		timestamp:      ts,
	}
	if err := badEvidence.Validate(); err == nil {
		t.Fatal("evidence with unknown event kind should be rejected")
	}

	// --- SanitizedEvidence: unknown outcome kind ---
	badEvidence2 := SanitizedEvidence{
		eventKind:      EventKindTextDelta,
		channel:        "ch",
		filterRule:     mustToken("filter.rule"),
		outcome:        FilterOutcomeKind("unknown_outcome"),
		descriptorCode: mustToken("desc.code"),
		fingerprint:    [32]byte{1},
		count:          0,
		offset:         0,
		timestamp:      ts,
	}
	if err := badEvidence2.Validate(); err == nil {
		t.Fatal("evidence with unknown outcome kind should be rejected")
	}

	// --- SanitizedEvidence: invalid filterRule token ---
	badEvidence3 := SanitizedEvidence{
		eventKind:      EventKindTextDelta,
		channel:        "ch",
		filterRule:     StableToken{value: "INVALID"},
		outcome:        FilterOutcomeKindEvaluated,
		descriptorCode: mustToken("desc.code"),
		fingerprint:    [32]byte{1},
		count:          0,
		offset:         0,
		timestamp:      ts,
	}
	if err := badEvidence3.Validate(); err == nil {
		t.Fatal("evidence with invalid filterRule token should be rejected")
	}

	// --- RecoveryIntent: invalid reason token ---
	badIntent := RecoveryIntent{
		strategy:  RecoveryStrategyExactReplay,
		directive: mustBuildExactDirective(t),
		reason:    StableToken{value: "INVALID"},
		priority:  0,
	}
	if err := badIntent.Validate(); err == nil {
		t.Fatal("intent with invalid reason token should be rejected")
	}
}

// TestTerminalValueValidationRejectsMalformedPrivateState verifies that directly
// assembled FailureCause, ExternalDescriptor, and TerminalResult values with
// invalid required tokens, malformed nested values, or over-long cause chains
// are rejected by Validate.
func TestTerminalValueValidationRejectsMalformedPrivateState(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)

	// --- FailureCause: invalid stage token ---
	badCause := FailureCause{
		stage:    StableToken{value: "INVALID"},
		code:     mustToken("code"),
		consumer: mustTokenOptional("consumer"),
		filter:   mustTokenOptional("filter"),
		ruleID:   mustTokenOptional("rule"),
	}
	if err := badCause.Validate(); err == nil {
		t.Fatal("cause with invalid stage should be rejected")
	}

	// --- FailureCause: invalid code token ---
	badCause2 := FailureCause{
		stage:    mustToken("stage"),
		code:     StableToken{value: "INVALID"},
		consumer: mustTokenOptional("consumer"),
		filter:   mustTokenOptional("filter"),
		ruleID:   mustTokenOptional("rule"),
	}
	if err := badCause2.Validate(); err == nil {
		t.Fatal("cause with invalid code should be rejected")
	}

	// --- ExternalDescriptor: invalid errorType token ---
	badDesc := ExternalDescriptor{
		errorType: StableToken{value: "INVALID"},
		code:      mustTokenOptional("code"),
		message:   mustToken("message"),
		param:     mustTokenOptional("param"),
	}
	if err := badDesc.Validate(); err == nil {
		t.Fatal("descriptor with invalid errorType should be rejected")
	}

	// --- ExternalDescriptor: invalid message token ---
	badDesc2 := ExternalDescriptor{
		errorType: mustToken("errortype"),
		code:      mustTokenOptional("code"),
		message:   StableToken{value: "INVALID"},
		param:     mustTokenOptional("param"),
	}
	if err := badDesc2.Validate(); err == nil {
		t.Fatal("descriptor with invalid message should be rejected")
	}

	// --- TerminalResult: error with invalid descriptor ---
	badTr := TerminalResult{
		channel:      "ch",
		err:          true,
		externalDesc: badDescPtr(),
		occurredAt:   ts,
	}
	if err := badTr.Validate(); err == nil {
		t.Fatal("error terminal with invalid descriptor should be rejected")
	}

	// --- TerminalResult: error with over-long cause chain ---
	overLongCauses := make([]FailureCause, MaxFailureCauses+1)
	for i := range overLongCauses {
		overLongCauses[i] = FailureCause{
			stage: mustToken("stage"),
			code:  mustToken("code"),
		}
	}
	badTr2 := TerminalResult{
		channel:       "ch",
		err:           true,
		externalDesc:  validDescPtr(),
		failureCauses: FailureCauseChain{causes: overLongCauses},
		occurredAt:    ts,
	}
	if err := badTr2.Validate(); err == nil {
		t.Fatal("error terminal with over-long cause chain should be rejected")
	}

	// --- TerminalResult: error with malformed cause ---
	badCauseChains := []FailureCause{
		{
			stage: mustToken("stage"),
			code:  mustToken("code"),
		},
		{
			stage: StableToken{value: "INVALID"},
			code:  mustToken("code"),
		},
	}
	badTr3 := TerminalResult{
		channel:       "ch",
		err:           true,
		externalDesc:  validDescPtr(),
		failureCauses: FailureCauseChain{causes: badCauseChains},
		occurredAt:    ts,
	}
	if err := badTr3.Validate(); err == nil {
		t.Fatal("error terminal with malformed nested cause should be rejected")
	}

	// --- Valid 4-entry error chain should pass ---
	validCauses := make([]FailureCause, MaxFailureCauses)
	for i := range validCauses {
		validCauses[i] = FailureCause{
			stage: mustToken("stage"),
			code:  mustToken("code"),
		}
	}
	validTr := TerminalResult{
		channel:       "ch",
		err:           true,
		externalDesc:  validDescPtr(),
		failureCauses: FailureCauseChain{causes: validCauses},
		occurredAt:    ts,
	}
	if err := validTr.Validate(); err != nil {
		t.Fatalf("valid 4-entry error chain failed Validate: %v", err)
	}

	// --- Valid success terminal without descriptor should pass ---
	validSuccess := TerminalResult{
		channel:    "ch",
		success:    true,
		occurredAt: ts,
	}
	if err := validSuccess.Validate(); err != nil {
		t.Fatalf("valid success terminal failed Validate: %v", err)
	}
}

// TestEvidenceBatchValidationRejectsMalformedStagedStart verifies that EvidenceBatch
// rejects invalid HTTP status codes in staged response-start regardless of
// terminal flag, and enforces transport_uncommitted for staged starts.
func TestEvidenceBatchValidationRejectsMalformedStagedStart(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)

	// --- Private status 99 (below min) ---
	badStart99 := ResponseStart{
		channel:   "ch",
		status:    99,
		timestamp: ts,
	}
	if _, err := NewEvidenceBatch(
		[]NormalizedEvent{},
		nil, nil,
		&badStart99, false,
		CommitStateTransportUncommitted, ts,
	); err == nil {
		t.Fatal("evidence batch with status 99 should be rejected")
	}

	// --- Private status 600 (above max) ---
	badStart600 := ResponseStart{
		channel:   "ch",
		status:    600,
		timestamp: ts,
	}
	if _, err := NewEvidenceBatch(
		[]NormalizedEvent{},
		nil, nil,
		&badStart600, false,
		CommitStateTransportUncommitted, ts,
	); err == nil {
		t.Fatal("evidence batch with status 600 should be rejected")
	}

	// --- Direct Validate on batch with bad staged start ---
	badBatch := EvidenceBatch{
		capturedAt:   ts,
		commitState:  CommitStateTransportUncommitted,
		stagedStart:  &badStart99,
		terminalFlag: false,
	}
	if err := badBatch.Validate(); err == nil {
		t.Fatal("batch with status 99 staged start should be rejected by Validate")
	}

	// --- Valid status 100 with transport_uncommitted should pass ---
	validStart := ResponseStart{
		channel:   "ch",
		status:    100,
		timestamp: ts,
	}
	if _, err := NewEvidenceBatch(
		[]NormalizedEvent{},
		nil, nil,
		&validStart, false,
		CommitStateTransportUncommitted, ts,
	); err != nil {
		t.Fatalf("valid staged start with transport_uncommitted failed: %v", err)
	}

	// --- Valid status 599 with transport_uncommitted should pass ---
	validStart2 := ResponseStart{
		channel:   "ch",
		status:    599,
		timestamp: ts,
	}
	if _, err := NewEvidenceBatch(
		[]NormalizedEvent{},
		nil, nil,
		&validStart2, false,
		CommitStateTransportUncommitted, ts,
	); err != nil {
		t.Fatalf("valid staged start with status 599 failed: %v", err)
	}

	// --- Staged start with stream_open should be rejected ---
	if _, err := NewEvidenceBatch(
		[]NormalizedEvent{},
		nil, nil,
		&validStart, false,
		CommitStateStreamOpen, ts,
	); err == nil {
		t.Fatal("staged start with stream_open should be rejected")
	}
}

// --- Helper functions ---

func buildValidDecision(t *testing.T) *FilterDecision {
	t.Helper()
	d, err := NewFilterDecision(
		FilterDecisionKindPass,
		"consumer.id", "filter.id", "rule.id",
		buildValidEvidence(t, time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
		nil,
	)
	if err != nil {
		t.Fatalf("buildValidDecision: %v", err)
	}
	return &d
}

func buildValidEvidence(t *testing.T, ts time.Time) SanitizedEvidence {
	t.Helper()
	e, err := NewSanitizedEvidence(
		EventKindTextDelta,
		"ch", "filter.rule", "desc.code",
		[32]byte{1},
		0, 0,
		FilterOutcomeKindEvaluated,
		ts,
	)
	if err != nil {
		t.Fatalf("buildValidEvidence: %v", err)
	}
	return e
}

func mustToken(v string) StableToken {
	tk, err := NewStableTokenRequired("test", v)
	if err != nil {
		panic(err)
	}
	return tk
}

func mustTokenOptional(v string) StableToken {
	tk, err := NewStableTokenOptional("test", v)
	if err != nil {
		panic(err)
	}
	return tk
}

func mustBuildExactDirective(t *testing.T) RecoveryDirective {
	t.Helper()
	d, err := NewRecoveryDirectiveExact("req.ref")
	if err != nil {
		t.Fatalf("mustBuildExactDirective: %v", err)
	}
	return d
}

func badDescPtr() *ExternalDescriptor {
	d := ExternalDescriptor{
		errorType: mustToken("errortype"),
		code:      mustTokenOptional("code"),
		message:   StableToken{value: "INVALID"},
		param:     mustTokenOptional("param"),
	}
	return &d
}

func validDescPtr() *ExternalDescriptor {
	d, err := NewExternalDescriptor("error.type", "code", "message", "")
	if err != nil {
		panic(err)
	}
	return &d
}

// TestNormalizedEventValidationEnforcesCauseBound verifies that the Validate
// method on NormalizedEvent rejects provider_error events whose failure cause
// chain exceeds MaxFailureCauses (4), while accepting chains of exactly 4.
// Invalid nested causes remain rejected as before.
func TestNormalizedEventValidationEnforcesCauseBound(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
	desc, err := NewExternalDescriptor("error.type", "code", "message", "")
	if err != nil {
		t.Fatalf("NewExternalDescriptor: %v", err)
	}

	// Build 4 valid causes (at the boundary).
	causes4 := make([]FailureCause, MaxFailureCauses)
	for i := range causes4 {
		causes4[i] = FailureCause{
			stage: mustToken("stage"),
			code:  mustToken("code"),
		}
	}
	chain4 := FailureCauseChain{causes: causes4}

	// Build 5 causes (exceeds boundary).
	causes5 := make([]FailureCause, MaxFailureCauses+1)
	for i := range causes5 {
		causes5[i] = FailureCause{
			stage: mustToken("stage"),
			code:  mustToken("code"),
		}
	}
	chain5 := FailureCauseChain{causes: causes5}

	// 4 causes must pass Validate.
	ev4 := NormalizedEvent{
		kind:          EventKindProviderError,
		channel:       "ch",
		timestamp:     ts,
		disposition:   BaseDispositionTerminalErrorCandidate,
		externalDesc:  &desc,
		failureCauses: chain4,
	}
	if err := ev4.Validate(); err != nil {
		t.Fatalf("4-cause provider error event should pass Validate: %v", err)
	}

	// 5 causes must fail Validate.
	ev5 := NormalizedEvent{
		kind:          EventKindProviderError,
		channel:       "ch",
		timestamp:     ts,
		disposition:   BaseDispositionTerminalErrorCandidate,
		externalDesc:  &desc,
		failureCauses: chain5,
	}
	if err := ev5.Validate(); err == nil {
		t.Fatal("5-cause provider error event should fail Validate")
	}

	// Invalid nested cause must still fail.
	badCauses := []FailureCause{
		{
			stage: mustToken("stage"),
			code:  mustToken("code"),
		},
		{
			stage: StableToken{value: "INVALID"},
			code:  mustToken("code"),
		},
	}
	badChain := FailureCauseChain{causes: badCauses}
	evBad := NormalizedEvent{
		kind:          EventKindProviderError,
		channel:       "ch",
		timestamp:     ts,
		disposition:   BaseDispositionTerminalErrorCandidate,
		externalDesc:  &desc,
		failureCauses: badChain,
	}
	if err := evBad.Validate(); err == nil {
		t.Fatal("provider error with invalid nested cause should fail Validate")
	}
}

// TestFilterDecisionConstructorCopiesRecoveryIntent verifies that NewFilterDecision
// defensively copies the caller-owned *RecoveryIntent so that subsequent
// variable reassignment of the original intent does not affect the returned
// FilterDecision or any previously created evaluated FilterOutcome.
func TestFilterDecisionConstructorCopiesRecoveryIntent(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
	evidence := buildValidEvidence(t, ts)

	directive, err := NewRecoveryDirectiveExact("req.ref")
	if err != nil {
		t.Fatalf("NewRecoveryDirectiveExact: %v", err)
	}
	intent, err := NewRecoveryIntent(RecoveryStrategyExactReplay, directive, "reason", 0)
	if err != nil {
		t.Fatalf("NewRecoveryIntent: %v", err)
	}

	decision, err := NewFilterDecision(
		FilterDecisionKindViolation,
		"consumer.id", "filter.id", "rule.id",
		evidence,
		&intent,
	)
	if err != nil {
		t.Fatalf("NewFilterDecision: %v", err)
	}

	// Create an evaluated outcome from the decision before any reassignment.
	outcome, err := NewFilterOutcomeEvaluated(decision)
	if err != nil {
		t.Fatalf("NewFilterOutcomeEvaluated: %v", err)
	}

	// Reassign the original intent variable to a zero value.
	intent = RecoveryIntent{}

	// The decision must still validate and retain its original intent values.
	if err := decision.Validate(); err != nil {
		t.Fatalf("decision should survive caller intent reassignment: %v", err)
	}
	d := decision.Intent()
	if d == nil {
		t.Fatal("decision Intent() returned nil")
	}
	if d.Strategy() != RecoveryStrategyExactReplay {
		t.Fatalf("decision Intent strategy should be exact_replay, got %s", d.Strategy())
	}
	if d.Reason() != "reason" {
		t.Fatalf("decision Intent reason should be 'reason', got %s", d.Reason())
	}
	if d.Priority() != 0 {
		t.Fatalf("decision Intent priority should be 0, got %d", d.Priority())
	}

	// The evaluated outcome must also survive and retain nested intent values.
	if err := outcome.Validate(); err != nil {
		t.Fatalf("outcome should survive caller intent reassignment: %v", err)
	}
	oDecision := outcome.Decision()
	if oDecision == nil {
		t.Fatal("outcome Decision() returned nil")
	}
	oIntent := oDecision.Intent()
	if oIntent == nil {
		t.Fatal("outcome decision Intent() returned nil")
	}
	if oIntent.Strategy() != RecoveryStrategyExactReplay {
		t.Fatalf("outcome decision Intent strategy should be exact_replay, got %s", oIntent.Strategy())
	}
	if oIntent.Reason() != "reason" {
		t.Fatalf("outcome decision Intent reason should be 'reason', got %s", oIntent.Reason())
	}
	if oIntent.Priority() != 0 {
		t.Fatalf("outcome decision Intent priority should be 0, got %d", oIntent.Priority())
	}
}

// TestFilterOutcomeValidationRejectsEachForbiddenField verifies that each
// FilterOutcome variant rejects exactly the forbidden field set for its kind
// when injected independently.
func TestFilterOutcomeValidationRejectsEachForbiddenField(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
	evidence := buildValidEvidence(t, ts)
	decision, err := NewFilterDecision(
		FilterDecisionKindPass,
		"consumer.id", "filter.id", "rule.id",
		evidence, nil,
	)
	if err != nil {
		t.Fatalf("NewFilterDecision: %v", err)
	}
	errCode := mustToken("err.code")

	tests := []struct {
		name string
		make func() FilterOutcome
	}{
		{
			name: "evaluated_with_errorCode",
			make: func() FilterOutcome {
				return FilterOutcome{
					kind:      FilterOutcomeKindEvaluated,
					decision:  &decision,
					errorCode: errCode,
				}
			},
		},
		{
			name: "evaluation_error_with_decision",
			make: func() FilterOutcome {
				return FilterOutcome{
					kind:      FilterOutcomeKindEvaluationError,
					decision:  &decision,
					errorCode: errCode,
				}
			},
		},
		{
			name: "not_applicable_with_decision",
			make: func() FilterOutcome {
				return FilterOutcome{
					kind:     FilterOutcomeKindNotApplicableForEpoch,
					decision: &decision,
				}
			},
		},
		{
			name: "not_applicable_with_errorCode",
			make: func() FilterOutcome {
				return FilterOutcome{
					kind:      FilterOutcomeKindNotApplicableForEpoch,
					errorCode: errCode,
				}
			},
		},
		{
			name: "deferred_with_decision",
			make: func() FilterOutcome {
				return FilterOutcome{
					kind:     FilterOutcomeKindDeferredByRequirement,
					decision: &decision,
				}
			},
		},
		{
			name: "deferred_with_errorCode",
			make: func() FilterOutcome {
				return FilterOutcome{
					kind:      FilterOutcomeKindDeferredByRequirement,
					errorCode: errCode,
				}
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if err := tc.make().Validate(); err == nil {
				t.Fatalf("%s should be rejected", tc.name)
			}
		})
	}
}

// TestRecoveryDirectiveValidationRejectsEachForbiddenField verifies that each
// RecoveryDirective variant rejects exactly the forbidden fields for its kind
// when injected independently.
func TestRecoveryDirectiveValidationRejectsEachForbiddenField(t *testing.T) {
	exactRef := mustToken("req.ref")
	snapRef := mustToken("snap.ref")
	schemaRef := mustToken("schema.ref")
	patchCode := mustToken("patch.code")

	tests := []struct {
		name string
		make func() RecoveryDirective
	}{
		{
			name: "exact_with_cursor",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:       RecoveryDirectiveKindExact,
					requestRef: exactRef,
					cursor:     5,
				}
			},
		},
		{
			name: "exact_with_snapshotRef",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:        RecoveryDirectiveKindExact,
					requestRef:  exactRef,
					snapshotRef: snapRef,
				}
			},
		},
		{
			name: "exact_with_schemaRef",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:       RecoveryDirectiveKindExact,
					requestRef: exactRef,
					schemaRef:  schemaRef,
				}
			},
		},
		{
			name: "exact_with_patchCode",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:       RecoveryDirectiveKindExact,
					requestRef: exactRef,
					patchCode:  patchCode,
				}
			},
		},
		{
			name: "continuation_with_requestRef",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:        RecoveryDirectiveKindContinuation,
					requestRef:  exactRef,
					snapshotRef: snapRef,
					cursor:      0,
				}
			},
		},
		{
			name: "continuation_with_schemaRef",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:        RecoveryDirectiveKindContinuation,
					snapshotRef: snapRef,
					schemaRef:   schemaRef,
					cursor:      0,
				}
			},
		},
		{
			name: "continuation_with_patchCode",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:        RecoveryDirectiveKindContinuation,
					snapshotRef: snapRef,
					patchCode:   patchCode,
					cursor:      0,
				}
			},
		},
		{
			name: "schema_with_requestRef",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:       RecoveryDirectiveKindSchema,
					requestRef: exactRef,
					schemaRef:  schemaRef,
					patchCode:  patchCode,
				}
			},
		},
		{
			name: "schema_with_cursor",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:      RecoveryDirectiveKindSchema,
					schemaRef: schemaRef,
					patchCode: patchCode,
					cursor:    5,
				}
			},
		},
		{
			name: "schema_with_snapshotRef",
			make: func() RecoveryDirective {
				return RecoveryDirective{
					kind:        RecoveryDirectiveKindSchema,
					snapshotRef: snapRef,
					schemaRef:   schemaRef,
					patchCode:   patchCode,
				}
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if err := tc.make().Validate(); err == nil {
				t.Fatalf("%s should be rejected", tc.name)
			}
		})
	}
}

// TestFilterAndTerminalConstructorsProduceValidValues verifies that all public
// constructors in the filter/terminal domain produce values that pass their
// own Validate, covering every normal variant matrix.
func TestFilterAndTerminalConstructorsProduceValidValues(t *testing.T) {
	ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)

	// --- FilterDecision: all kinds with valid evidence ---
	evidence := buildValidEvidence(t, ts)
	kinds := []FilterDecisionKind{
		FilterDecisionKindPass,
		FilterDecisionKindObserve,
		FilterDecisionKindViolation,
		FilterDecisionKindFatal,
		FilterDecisionKindReplacement,
	}
	for _, k := range kinds {
		t.Run("FilterDecision_"+string(k), func(t *testing.T) {
			d, err := NewFilterDecision(k, "consumer.id", "filter.id", "rule.id", evidence, nil)
			if err != nil {
				t.Fatalf("NewFilterDecision %s: %v", k, err)
			}
			if err := d.Validate(); err != nil {
				t.Fatalf("FilterDecision %s failed Validate: %v", k, err)
			}
		})
	}

	// --- FilterDecision with violation + recovery intent ---
	directive, err := NewRecoveryDirectiveExact("req.ref")
	if err != nil {
		t.Fatalf("NewRecoveryDirectiveExact: %v", err)
	}
	intent, err := NewRecoveryIntent(RecoveryStrategyExactReplay, directive, "reason", 0)
	if err != nil {
		t.Fatalf("NewRecoveryIntent: %v", err)
	}
	d, err := NewFilterDecision(FilterDecisionKindViolation, "consumer.id", "filter.id", "rule.id", evidence, &intent)
	if err != nil {
		t.Fatalf("NewFilterDecision violation+intent: %v", err)
	}
	if err := d.Validate(); err != nil {
		t.Fatalf("FilterDecision violation+intent failed Validate: %v", err)
	}

	// --- FilterOutcome: all kinds ---
	t.Run("FilterOutcome_evaluated", func(t *testing.T) {
		o, err := NewFilterOutcomeEvaluated(d)
		if err != nil {
			t.Fatalf("NewFilterOutcomeEvaluated: %v", err)
		}
		if err := o.Validate(); err != nil {
			t.Fatalf("FilterOutcome evaluated failed Validate: %v", err)
		}
	})
	t.Run("FilterOutcome_evaluation_error", func(t *testing.T) {
		o, err := NewFilterOutcomeEvaluationError("err.code")
		if err != nil {
			t.Fatalf("NewFilterOutcomeEvaluationError: %v", err)
		}
		if err := o.Validate(); err != nil {
			t.Fatalf("FilterOutcome evaluation_error failed Validate: %v", err)
		}
	})
	t.Run("FilterOutcome_not_applicable", func(t *testing.T) {
		o := NewFilterOutcomeNotApplicableForEpoch()
		if err := o.Validate(); err != nil {
			t.Fatalf("FilterOutcome not_applicable failed Validate: %v", err)
		}
	})
	t.Run("FilterOutcome_deferred", func(t *testing.T) {
		o := NewFilterOutcomeDeferredByRequirement()
		if err := o.Validate(); err != nil {
			t.Fatalf("FilterOutcome deferred failed Validate: %v", err)
		}
	})

	// --- RecoveryDirective: all kinds ---
	t.Run("RecoveryDirective_exact", func(t *testing.T) {
		dir, err := NewRecoveryDirectiveExact("req.ref")
		if err != nil {
			t.Fatalf("NewRecoveryDirectiveExact: %v", err)
		}
		if err := dir.Validate(); err != nil {
			t.Fatalf("RecoveryDirective exact failed Validate: %v", err)
		}
	})
	t.Run("RecoveryDirective_continuation", func(t *testing.T) {
		dir, err := NewRecoveryDirectiveContinuation(10, "snap.ref")
		if err != nil {
			t.Fatalf("NewRecoveryDirectiveContinuation: %v", err)
		}
		if err := dir.Validate(); err != nil {
			t.Fatalf("RecoveryDirective continuation failed Validate: %v", err)
		}
	})
	t.Run("RecoveryDirective_schema", func(t *testing.T) {
		dir, err := NewRecoveryDirectiveSchema("schema.ref", "patch.code")
		if err != nil {
			t.Fatalf("NewRecoveryDirectiveSchema: %v", err)
		}
		if err := dir.Validate(); err != nil {
			t.Fatalf("RecoveryDirective schema failed Validate: %v", err)
		}
	})

	// --- RecoveryIntent: all strategies ---
	strategies := []struct {
		strategy  RecoveryStrategy
		directive RecoveryDirective
	}{
		{RecoveryStrategyExactReplay, mustBuildExactDirective(t)},
		{RecoveryStrategyContinuationRepair, mustBuildContinuationDirective(t)},
		{RecoveryStrategySchemaRepair, mustBuildSchemaDirective(t)},
		{RecoveryStrategyManagedContinuation, mustBuildContinuationDirective(t)},
	}
	for _, s := range strategies {
		t.Run("RecoveryIntent_"+string(s.strategy), func(t *testing.T) {
			intent, err := NewRecoveryIntent(s.strategy, s.directive, "reason", 0)
			if err != nil {
				t.Fatalf("NewRecoveryIntent %s: %v", s.strategy, err)
			}
			if err := intent.Validate(); err != nil {
				t.Fatalf("RecoveryIntent %s failed Validate: %v", s.strategy, err)
			}
		})
	}

	// --- SanitizedEvidence: valid variant ---
	t.Run("SanitizedEvidence", func(t *testing.T) {
		e, err := NewSanitizedEvidence(
			EventKindTextDelta, "ch", "filter.rule", "desc.code",
			[32]byte{1}, 0, 0,
			FilterOutcomeKindEvaluated, ts,
		)
		if err != nil {
			t.Fatalf("NewSanitizedEvidence: %v", err)
		}
		if err := e.Validate(); err != nil {
			t.Fatalf("SanitizedEvidence failed Validate: %v", err)
		}
	})

	// --- FailureCause: required-only (stage + code, optional fields empty) ---
	t.Run("FailureCause_required_only", func(t *testing.T) {
		fc, err := NewFailureCause("stage", "code", "", "", "")
		if err != nil {
			t.Fatalf("NewFailureCause required-only: %v", err)
		}
		if err := fc.Validate(); err != nil {
			t.Fatalf("FailureCause required-only failed Validate: %v", err)
		}
	})

	// --- FailureCause: optional-full (all fields provided) ---
	t.Run("FailureCause_optional_full", func(t *testing.T) {
		fc, err := NewFailureCause("stage", "code", "consumer", "filter", "rule")
		if err != nil {
			t.Fatalf("NewFailureCause optional-full: %v", err)
		}
		if err := fc.Validate(); err != nil {
			t.Fatalf("FailureCause optional-full failed Validate: %v", err)
		}
	})

	// --- ExternalDescriptor: required-only (errorType + message, optional fields empty) ---
	t.Run("ExternalDescriptor_required_only", func(t *testing.T) {
		ed, err := NewExternalDescriptor("error.type", "", "message", "")
		if err != nil {
			t.Fatalf("NewExternalDescriptor required-only: %v", err)
		}
		if err := ed.Validate(); err != nil {
			t.Fatalf("ExternalDescriptor required-only failed Validate: %v", err)
		}
	})

	// --- ExternalDescriptor: optional-full (all fields provided) ---
	t.Run("ExternalDescriptor_optional_full", func(t *testing.T) {
		ed, err := NewExternalDescriptor("error.type", "code", "message", "param")
		if err != nil {
			t.Fatalf("NewExternalDescriptor optional-full: %v", err)
		}
		if err := ed.Validate(); err != nil {
			t.Fatalf("ExternalDescriptor optional-full failed Validate: %v", err)
		}
	})

	// --- Invalid inputs must be rejected by constructors ---
	t.Run("NewFilterDecision_unknownKind", func(t *testing.T) {
		_, err := NewFilterDecision(FilterDecisionKind("bad"), "consumer.id", "filter.id", "rule.id", evidence, nil)
		if err == nil {
			t.Fatal("NewFilterDecision unknown kind should return error")
		}
	})
	t.Run("NewFilterDecision_nonViolationWithIntent", func(t *testing.T) {
		_, err := NewFilterDecision(FilterDecisionKindPass, "consumer.id", "filter.id", "rule.id", evidence, &intent)
		if err == nil {
			t.Fatal("NewFilterDecision non-violation with intent should return error")
		}
	})
	t.Run("NewFilterOutcomeEvaluated_invalidDecision", func(t *testing.T) {
		badDecision := FilterDecision{
			kind:       FilterDecisionKindPass,
			consumerID: StableToken{value: "INVALID"},
			filterID:   mustToken("filter.id"),
			ruleID:     mustToken("rule.id"),
			evidence:   evidence,
		}
		_, err := NewFilterOutcomeEvaluated(badDecision)
		if err == nil {
			t.Fatal("NewFilterOutcomeEvaluated with invalid decision should return error")
		}
	})
	t.Run("NewFilterOutcomeEvaluationError_emptyCode", func(t *testing.T) {
		_, err := NewFilterOutcomeEvaluationError("")
		if err == nil {
			t.Fatal("NewFilterOutcomeEvaluationError with empty code should return error")
		}
	})
	t.Run("NewRecoveryDirectiveExact_emptyRequestID", func(t *testing.T) {
		_, err := NewRecoveryDirectiveExact("")
		if err == nil {
			t.Fatal("NewRecoveryDirectiveExact with empty requestID should return error")
		}
	})
	t.Run("NewRecoveryDirectiveContinuation_negativeCursor", func(t *testing.T) {
		_, err := NewRecoveryDirectiveContinuation(-1, "snap.ref")
		if err == nil {
			t.Fatal("NewRecoveryDirectiveContinuation with negative cursor should return error")
		}
	})
	t.Run("NewRecoveryDirectiveSchema_emptySchemaRef", func(t *testing.T) {
		_, err := NewRecoveryDirectiveSchema("", "patch.code")
		if err == nil {
			t.Fatal("NewRecoveryDirectiveSchema with empty schemaRef should return error")
		}
	})
	t.Run("NewRecoveryIntent_emptyReason", func(t *testing.T) {
		exactDir, err := NewRecoveryDirectiveExact("req.ref")
		if err != nil {
			t.Fatalf("NewRecoveryDirectiveExact: %v", err)
		}
		_, err = NewRecoveryIntent(RecoveryStrategyExactReplay, exactDir, "", 0)
		if err == nil {
			t.Fatal("NewRecoveryIntent with empty reason should return error")
		}
	})
	t.Run("NewSanitizedEvidence_zeroFingerprint", func(t *testing.T) {
		_, err := NewSanitizedEvidence(
			EventKindTextDelta, "ch", "filter.rule", "desc.code",
			[32]byte{}, 0, 0,
			FilterOutcomeKindEvaluated, ts,
		)
		if err == nil {
			t.Fatal("NewSanitizedEvidence with zero fingerprint should return error")
		}
	})
	t.Run("NewFailureCause_invalidStage", func(t *testing.T) {
		_, err := NewFailureCause("INVALID", "code", "", "", "")
		if err == nil {
			t.Fatal("NewFailureCause invalid stage should return error")
		}
	})
	t.Run("NewExternalDescriptor_invalidErrorType", func(t *testing.T) {
		_, err := NewExternalDescriptor("INVALID", "code", "message", "")
		if err == nil {
			t.Fatal("NewExternalDescriptor invalid errorType should return error")
		}
	})
}

// --- Additional helpers for TestFilterAndTerminalConstructorsProduceValidValues ---

func mustBuildContinuationDirective(t *testing.T) RecoveryDirective {
	t.Helper()
	d, err := NewRecoveryDirectiveContinuation(10, "snap.ref")
	if err != nil {
		t.Fatalf("mustBuildContinuationDirective: %v", err)
	}
	return d
}

func mustBuildSchemaDirective(t *testing.T) RecoveryDirective {
	t.Helper()
	d, err := NewRecoveryDirectiveSchema("schema.ref", "patch.code")
	if err != nil {
		t.Fatalf("mustBuildSchemaDirective: %v", err)
	}
	return d
}
