package connectorcapabilities

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"sync"
	"testing"
)

// --- Stub types ---

type stubVerifier struct{ called bool }

func (s *stubVerifier) Verify(_ context.Context, _ *VerifierInput) error {
	s.called = true
	return nil
}

type stubParser struct {
	called bool
	out    []ConnectorEvent
}

func (s *stubParser) Parse(_ context.Context, _ *ParserInput) ([]ConnectorEvent, error) {
	s.called = true
	return s.out, nil
}

// --- Gate stubs ---

func gateActive(_ context.Context, _ string, _ CapabilityKind, _ string) (bool, error) {
	return true, nil
}

func gateInactive(_ context.Context, _ string, _ CapabilityKind, _ string) (bool, error) {
	return false, nil
}

func gateError(err error) CapabilityGate {
	return func(context.Context, string, CapabilityKind, string) (bool, error) { return false, err }
}

// resetDefaultRegistry resets the package-level default registry state for
// test isolation. Must be called from t.Cleanup.
func resetDefaultRegistry(t *testing.T) {
	t.Helper()
	defaultGateBindMu.Lock()
	defer defaultGateBindMu.Unlock()
	defaultRegistry = NewRegistry(nil)
	defaultGateBound = false
}

// --- Test 1: Closed-set enum truth table ---

func TestCapabilityKind_IsKnown_TruthTable(t *testing.T) {
	cases := []struct {
		kind CapabilityKind
		want bool
	}{
		{SignatureVerifier, true},
		{EnvelopeParser, true},
		{"", false},
		{"unknown_kind", false},
		{"signature_VERIFIER", false}, // case-sensitive
	}
	for _, tc := range cases {
		got := tc.kind.IsKnown()
		if got != tc.want {
			t.Errorf("IsKnown(%q) = %v, want %v", tc.kind, got, tc.want)
		}
	}
}

// --- Test 2: RegisterSignatureVerifier + ResolveSignatureVerifier happy path ---

func TestRegisterAndResolveSignatureVerifier_HappyPath(t *testing.T) {
	reg := NewRegistry(gateActive)
	h := &stubVerifier{}
	if err := reg.RegisterSignatureVerifier("ns1", h); err != nil {
		t.Fatalf("register: %v", err)
	}
	got, err := reg.ResolveSignatureVerifier(context.Background(), "ns1", "")
	if err != nil {
		t.Fatalf("resolve: %v", err)
	}
	if got != h {
		t.Fatal("resolved handler is not the registered handler")
	}
}

// --- Test 3: RegisterEnvelopeParser + ResolveEnvelopeParser happy path ---

func TestRegisterAndResolveEnvelopeParser_HappyPath(t *testing.T) {
	reg := NewRegistry(gateActive)
	h := &stubParser{}
	if err := reg.RegisterEnvelopeParser("ns2", h); err != nil {
		t.Fatalf("register: %v", err)
	}
	got, err := reg.ResolveEnvelopeParser(context.Background(), "ns2", "")
	if err != nil {
		t.Fatalf("resolve: %v", err)
	}
	if got != h {
		t.Fatal("resolved handler is not the registered handler")
	}
}

// --- Test 4: RegisterSignatureVerifier empty namespace ---

func TestRegisterSignatureVerifier_EmptyNamespace_ErrEmptyNamespace(t *testing.T) {
	reg := NewRegistry(gateActive)
	err := reg.RegisterSignatureVerifier("", &stubVerifier{})
	if !errors.Is(err, ErrEmptyNamespace) {
		t.Fatalf("expected ErrEmptyNamespace, got %v", err)
	}
}

// --- Test 5: RegisterSignatureVerifier nil handler ---

func TestRegisterSignatureVerifier_NilHandler_ErrNilHandler(t *testing.T) {
	reg := NewRegistry(gateActive)
	err := reg.RegisterSignatureVerifier("ns", nil)
	if !errors.Is(err, ErrNilHandler) {
		t.Fatalf("expected ErrNilHandler, got %v", err)
	}
}

// TestRegisterSignatureVerifier_TypedNilHandler_ErrNilHandler regresses Codex
// Review BLOCK round-1 on PR #1521: a typed-nil pointer like
// `var h *stubVerifier = nil` is NOT caught by `handler == nil` (the interface
// holding the nil pointer is itself non-nil). Without isNilHandler reflection,
// such a registration would succeed and Resolve* would later panic when the
// handler is dereferenced. Both direct and polymorphic registration paths
// must fail closed.
func TestRegisterSignatureVerifier_TypedNilHandler_ErrNilHandler(t *testing.T) {
	reg := NewRegistry(gateActive)

	var typedNil *stubVerifier
	if err := reg.RegisterSignatureVerifier("ns", typedNil); !errors.Is(err, ErrNilHandler) {
		t.Fatalf("direct register typed-nil verifier: expected ErrNilHandler, got %v", err)
	}

	// Same handler shape via RegisterCapability polymorphic path.
	if err := reg.RegisterCapability("ns", SignatureVerifier, typedNil); !errors.Is(err, ErrNilHandler) {
		t.Fatalf("RegisterCapability typed-nil verifier: expected ErrNilHandler, got %v", err)
	}
}

func TestRegisterEnvelopeParser_TypedNilHandler_ErrNilHandler(t *testing.T) {
	reg := NewRegistry(gateActive)

	var typedNil *stubParser
	if err := reg.RegisterEnvelopeParser("ns", typedNil); !errors.Is(err, ErrNilHandler) {
		t.Fatalf("direct register typed-nil parser: expected ErrNilHandler, got %v", err)
	}

	if err := reg.RegisterCapability("ns", EnvelopeParser, typedNil); !errors.Is(err, ErrNilHandler) {
		t.Fatalf("RegisterCapability typed-nil parser: expected ErrNilHandler, got %v", err)
	}
}

// --- Test 6: Duplicate RegisterSignatureVerifier, first handler preserved ---

func TestRegisterSignatureVerifier_Duplicate_ErrAlreadyRegistered_FirstHandlerPreserved(t *testing.T) {
	reg := NewRegistry(gateActive)
	first := &stubVerifier{}
	second := &stubVerifier{}

	if err := reg.RegisterSignatureVerifier("ns", first); err != nil {
		t.Fatalf("first register: %v", err)
	}
	err := reg.RegisterSignatureVerifier("ns", second)
	if !errors.Is(err, ErrCapabilityAlreadyRegistered) {
		t.Fatalf("expected ErrCapabilityAlreadyRegistered, got %v", err)
	}

	// Assert first handler is still installed (not replaced by second).
	got, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "")
	if err != nil {
		t.Fatalf("resolve: %v", err)
	}
	if got != first {
		t.Fatal("duplicate register replaced the first handler")
	}
}

// --- Test 7: Duplicate RegisterEnvelopeParser ---

func TestRegisterEnvelopeParser_Duplicate_ErrAlreadyRegistered_FirstHandlerPreserved(t *testing.T) {
	reg := NewRegistry(gateActive)
	first := &stubParser{}
	second := &stubParser{}

	if err := reg.RegisterEnvelopeParser("ns", first); err != nil {
		t.Fatalf("first register: %v", err)
	}
	err := reg.RegisterEnvelopeParser("ns", second)
	if !errors.Is(err, ErrCapabilityAlreadyRegistered) {
		t.Fatalf("expected ErrCapabilityAlreadyRegistered, got %v", err)
	}

	got, err := reg.ResolveEnvelopeParser(context.Background(), "ns", "")
	if err != nil {
		t.Fatalf("resolve: %v", err)
	}
	if got != first {
		t.Fatal("duplicate register replaced the first handler")
	}
}

func TestUpsertCapability_ReplacesExistingSignatureVerifier(t *testing.T) {
	reg := NewRegistry(gateActive)
	first := &stubVerifier{}
	second := &stubVerifier{}

	if err := reg.UpsertCapability("ns", SignatureVerifier, first); err != nil {
		t.Fatalf("first upsert: %v", err)
	}
	if err := reg.UpsertCapability("NS", SignatureVerifier, second); err != nil {
		t.Fatalf("second upsert: %v", err)
	}

	got, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "")
	if err != nil {
		t.Fatalf("resolve: %v", err)
	}
	if got != second {
		t.Fatal("upsert did not replace the existing signature verifier")
	}
}

func TestUnregisterCapability_RemovesEnvelopeParser(t *testing.T) {
	reg := NewRegistry(gateActive)
	parser := &stubParser{}

	if err := reg.UpsertCapability("ns", EnvelopeParser, parser); err != nil {
		t.Fatalf("upsert parser: %v", err)
	}
	if removed := reg.UnregisterCapability("NS", EnvelopeParser); !removed {
		t.Fatal("UnregisterCapability = false, want true")
	}
	_, err := reg.ResolveEnvelopeParser(context.Background(), "ns", "")
	if !errors.Is(err, ErrCapabilityNotAvailable) {
		t.Fatalf("ResolveEnvelopeParser after unregister = %v, want ErrCapabilityNotAvailable", err)
	}
	if removed := reg.UnregisterCapability("ns", EnvelopeParser); removed {
		t.Fatal("second UnregisterCapability = true, want false")
	}
}

func TestResolveSignatureVerifier_RuntimePackInstallIDRejectsUnboundProjection(t *testing.T) {
	reg := NewRegistry(gateActive)
	h := &stubVerifier{}
	if err := reg.RegisterSignatureVerifier("ns", h); err != nil {
		t.Fatalf("register: %v", err)
	}

	_, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "install-a")
	if !errors.Is(err, ErrCapabilityIncarnationChanged) {
		t.Fatalf("ResolveSignatureVerifier with runtime packInstallID = %v, want ErrCapabilityIncarnationChanged", err)
	}

	got, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "")
	if err != nil {
		t.Fatalf("resolve with empty packInstallID: %v", err)
	}
	if got != h {
		t.Fatal("empty packInstallID path should still resolve the registered admin/test projection")
	}
}

func TestResolveEnvelopeParser_RuntimePackInstallIDRequiresMatchingProjection(t *testing.T) {
	reg := NewRegistry(gateActive)
	legacy := &stubParser{}
	if err := reg.UpsertCapability("ns", EnvelopeParser, legacy); err != nil {
		t.Fatalf("legacy upsert: %v", err)
	}
	_, err := reg.ResolveEnvelopeParser(context.Background(), "ns", "install-a")
	if !errors.Is(err, ErrCapabilityIncarnationChanged) {
		t.Fatalf("legacy ResolveEnvelopeParser with runtime packInstallID = %v, want ErrCapabilityIncarnationChanged", err)
	}

	current := &stubParser{}
	if err := reg.UpsertCapabilityForPackInstall("ns", EnvelopeParser, "install-a", current); err != nil {
		t.Fatalf("pack-install upsert: %v", err)
	}
	got, err := reg.ResolveEnvelopeParser(context.Background(), "ns", "install-a")
	if err != nil {
		t.Fatalf("resolve matching packInstallID: %v", err)
	}
	if got != current {
		t.Fatal("matching packInstallID did not resolve the current projection")
	}
}

// --- Test 8: RegisterCapability unknown kind ---

func TestRegisterCapability_UnknownKind_ErrUnknownCapabilityKind(t *testing.T) {
	reg := NewRegistry(gateActive)
	err := reg.RegisterCapability("ns", "unknown_kind", &stubVerifier{})
	if !errors.Is(err, ErrUnknownCapabilityKind) {
		t.Fatalf("expected ErrUnknownCapabilityKind, got %v", err)
	}
}

// --- Test 9: RegisterCapability wrong handler type ---

func TestRegisterCapability_WrongHandlerType_WrappedError(t *testing.T) {
	reg := NewRegistry(gateActive)
	// Pass a parser where a verifier is expected.
	err := reg.RegisterCapability("ns", SignatureVerifier, &stubParser{})
	if err == nil {
		t.Fatal("expected error for wrong handler type, got nil")
	}
	if !strings.Contains(err.Error(), "does not implement") {
		t.Fatalf("error should mention 'does not implement', got: %v", err)
	}
}

// --- Test 10: RegisterCapability same namespace, both kinds ---

func TestRegisterCapability_BothKindsSameNamespace_BothResolve(t *testing.T) {
	reg := NewRegistry(gateActive)
	v := &stubVerifier{}
	p := &stubParser{}

	if err := reg.RegisterCapability("ns", SignatureVerifier, v); err != nil {
		t.Fatalf("register verifier: %v", err)
	}
	if err := reg.RegisterCapability("ns", EnvelopeParser, p); err != nil {
		t.Fatalf("register parser: %v", err)
	}

	ctx := context.Background()
	gotV, err := reg.ResolveSignatureVerifier(ctx, "ns", "")
	if err != nil {
		t.Fatalf("resolve verifier: %v", err)
	}
	if gotV != v {
		t.Fatal("verifier mismatch")
	}

	gotP, err := reg.ResolveEnvelopeParser(ctx, "ns", "")
	if err != nil {
		t.Fatalf("resolve parser: %v", err)
	}
	if gotP != p {
		t.Fatal("parser mismatch")
	}
}

// --- Test 11: ResolveSignatureVerifier gate returns (false, nil) ---

func TestResolveSignatureVerifier_GateFalse_ErrCapabilityNotAvailable(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if err := reg.RegisterSignatureVerifier("ns", &stubVerifier{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	_, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "")
	if !errors.Is(err, ErrCapabilityNotAvailable) {
		t.Fatalf("expected ErrCapabilityNotAvailable, got %v", err)
	}
}

// --- Test 12: ResolveEnvelopeParser gate returns (false, nil) ---

func TestResolveEnvelopeParser_GateFalse_ErrCapabilityNotAvailable(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if err := reg.RegisterEnvelopeParser("ns", &stubParser{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	_, err := reg.ResolveEnvelopeParser(context.Background(), "ns", "")
	if !errors.Is(err, ErrCapabilityNotAvailable) {
		t.Fatalf("expected ErrCapabilityNotAvailable, got %v", err)
	}
}

// --- Test 13: ResolveSignatureVerifier gate returns sentinel error ---

func TestResolveSignatureVerifier_GateError_PropagatesViaErrorsIs(t *testing.T) {
	sentinelErr := errors.New("sentinel db error")
	reg := NewRegistry(gateError(sentinelErr))
	if err := reg.RegisterSignatureVerifier("ns", &stubVerifier{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	_, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "")
	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !errors.Is(err, sentinelErr) {
		t.Fatalf("expected error to wrap sentinelErr via errors.Is, got: %v", err)
	}
}

// --- Test 14: ResolveSignatureVerifier on NewRegistry(nil) → ErrGateNotBound ---

func TestResolveSignatureVerifier_NilGate_ErrGateNotBound(t *testing.T) {
	reg := NewRegistry(nil)
	if err := reg.RegisterSignatureVerifier("ns", &stubVerifier{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	_, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "")
	if !errors.Is(err, ErrGateNotBound) {
		t.Fatalf("expected ErrGateNotBound, got %v", err)
	}
}

// --- Test 15: ResolveSignatureVerifier empty namespace → ErrEmptyNamespace ---

func TestResolveSignatureVerifier_EmptyNamespace_ErrEmptyNamespace(t *testing.T) {
	reg := NewRegistry(gateActive)
	_, err := reg.ResolveSignatureVerifier(context.Background(), "", "")
	if !errors.Is(err, ErrEmptyNamespace) {
		t.Fatalf("expected ErrEmptyNamespace, got %v", err)
	}
}

// --- Test 16: BindDefaultCapabilityGate(nil) → ErrGateNotBound ---

func TestBindDefaultCapabilityGate_NilGate_ErrGateNotBound(t *testing.T) {
	err := BindDefaultCapabilityGate(nil)
	if !errors.Is(err, ErrGateNotBound) {
		t.Fatalf("expected ErrGateNotBound, got %v", err)
	}
}

// --- Test 17: BindDefaultCapabilityGate rebind → already-bound error ---

func TestBindDefaultCapabilityGate_Rebind_AlreadyBoundError(t *testing.T) {
	t.Cleanup(func() { resetDefaultRegistry(t) })
	// Reset state before the test to ensure it is order-independent.
	resetDefaultRegistry(t)

	if err := BindDefaultCapabilityGate(gateActive); err != nil {
		t.Fatalf("first bind: %v", err)
	}
	err := BindDefaultCapabilityGate(gateActive)
	if err == nil {
		t.Fatal("second bind should have returned an error")
	}
	if !strings.Contains(err.Error(), "already bound") {
		t.Fatalf("error should mention 'already bound', got: %v", err)
	}
}

// --- Test 18: Race coverage ---

func TestRegistry_GateField_NoRaceUnderConcurrentBindAndResolve(t *testing.T) {
	t.Cleanup(func() { resetDefaultRegistry(t) })
	resetDefaultRegistry(t)

	const ns = "race-ns"
	_ = Default().RegisterSignatureVerifier(ns, &stubVerifier{})

	var wg sync.WaitGroup
	ctx := context.Background()

	// Goroutine 1: bind the gate.
	wg.Add(1)
	go func() {
		defer wg.Done()
		_ = BindDefaultCapabilityGate(gateActive)
	}()

	// Goroutine 2: attempt resolve (may fail if gate not yet bound — that is ok).
	wg.Add(1)
	go func() {
		defer wg.Done()
		_, _ = Default().ResolveSignatureVerifier(ctx, ns, "")
	}()

	// Goroutine 3: attempt resolve for parser path.
	wg.Add(1)
	go func() {
		defer wg.Done()
		_, _ = Default().ResolveEnvelopeParser(ctx, ns, "")
	}()

	wg.Wait()
	// -race detector validates no data race occurred.
}

// --- Test 20: Case-insensitive registration and resolution ---

// TestRegisterResolve_CaseInsensitive verifies that registration under a
// mixed-case namespace key (e.g. "MyConnector") and resolution under the
// lowercase form (e.g. "myconnector") return the same handler, and vice-versa.
// This is the Fix 1 invariant: normalization lives inside the registry, not at
// call sites.
func TestRegisterResolve_CaseInsensitive(t *testing.T) {
	reg := NewRegistry(gateActive)
	v := &stubVerifier{}
	p := &stubParser{}

	// Register under mixed-case — registry must lowercase before storing.
	if err := reg.RegisterSignatureVerifier("MyConnector", v); err != nil {
		t.Fatalf("register verifier: %v", err)
	}
	if err := reg.RegisterEnvelopeParser("MyConnector", p); err != nil {
		t.Fatalf("register parser: %v", err)
	}

	ctx := context.Background()

	// Resolve under the canonical lowercase form → must find the same handler.
	gotV, err := reg.ResolveSignatureVerifier(ctx, "myconnector", "")
	if err != nil {
		t.Fatalf("resolve verifier (lowercase): %v", err)
	}
	if gotV != v {
		t.Fatal("ResolveSignatureVerifier(lowercase) returned wrong handler")
	}
	gotP, err := reg.ResolveEnvelopeParser(ctx, "myconnector", "")
	if err != nil {
		t.Fatalf("resolve parser (lowercase): %v", err)
	}
	if gotP != p {
		t.Fatal("ResolveEnvelopeParser(lowercase) returned wrong handler")
	}

	// Resolve under the original mixed-case form → also works (normalized to same key).
	gotV2, err := reg.ResolveSignatureVerifier(ctx, "MyConnector", "")
	if err != nil {
		t.Fatalf("resolve verifier (mixed-case): %v", err)
	}
	if gotV2 != v {
		t.Fatal("ResolveSignatureVerifier(mixed-case) returned wrong handler")
	}

	// Duplicate registration under a different case form must fail with
	// ErrCapabilityAlreadyRegistered (both normalize to the same key).
	if err := reg.RegisterSignatureVerifier("MYCONNECTOR", &stubVerifier{}); !errors.Is(err, ErrCapabilityAlreadyRegistered) {
		t.Fatalf("expected ErrCapabilityAlreadyRegistered for same-key re-register, got %v", err)
	}

	// A never-registered namespace must return ErrCapabilityNotAvailable.
	_, err = reg.ResolveSignatureVerifier(ctx, "neverregistered", "")
	if !errors.Is(err, ErrCapabilityNotAvailable) {
		t.Fatalf("expected ErrCapabilityNotAvailable for unregistered namespace, got %v", err)
	}
}

// --- Test 19: Source-undeclared but handler registered → ErrCapabilityNotAvailable ---

func TestResolve_HandlerRegisteredButSourceUndeclared_NotAvailable(t *testing.T) {
	// Gate always returns (false, nil) — simulates source-declaration missing.
	// This is the load-bearing semantic of the two-surface design: handler
	// existence alone does NOT authorize dispatch.
	reg := NewRegistry(gateInactive)

	if err := reg.RegisterCapability("ns", SignatureVerifier, &stubVerifier{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	_, err := reg.ResolveSignatureVerifier(context.Background(), "ns", "")
	if !errors.Is(err, ErrCapabilityNotAvailable) {
		t.Fatalf("expected ErrCapabilityNotAvailable for source-undeclared handler, got %v", err)
	}
}

// --- Test 20: HasHandler — Surface-1-only existence query ---

func TestHasHandler_RegisteredNamespaceKind_ReturnsTrue(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if err := reg.RegisterCapability("acme", SignatureVerifier, &stubVerifier{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	if !reg.HasHandler("acme", SignatureVerifier) {
		t.Fatal("HasHandler(acme, signature_verifier) = false, want true")
	}
}

func TestHasHandler_CaseInsensitive(t *testing.T) {
	// Register with mixed-case namespace; HasHandler with lowercase must find it
	// because Register normalises to lowercase before storing.
	reg := NewRegistry(gateInactive)
	if err := reg.RegisterCapability("MyNs", SignatureVerifier, &stubVerifier{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	if !reg.HasHandler("myns", SignatureVerifier) {
		t.Fatal("HasHandler(myns, signature_verifier) = false after Register(MyNs), want true")
	}
	// Also check HasHandler("MyNs") works (normalised match).
	if !reg.HasHandler("MyNs", SignatureVerifier) {
		t.Fatal("HasHandler(MyNs, signature_verifier) = false, want true")
	}
}

func TestHasHandler_UnregisteredNamespace_ReturnsFalse(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if reg.HasHandler("unknown", SignatureVerifier) {
		t.Fatal("HasHandler(unknown, signature_verifier) = true, want false")
	}
}

func TestHasHandler_WrongKind_ReturnsFalse(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if err := reg.RegisterCapability("acme", SignatureVerifier, &stubVerifier{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	// Registered as verifier, queried as parser — should be false.
	if reg.HasHandler("acme", EnvelopeParser) {
		t.Fatal("HasHandler(acme, envelope_parser) = true after verifier-only registration, want false")
	}
}

func TestHasHandler_UnknownKind_ReturnsFalse(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if reg.HasHandler("acme", CapabilityKind("unknown_kind")) {
		t.Fatal("HasHandler with unknown kind = true, want false")
	}
}

func TestHasHandler_EnvelopeParser(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if err := reg.RegisterCapability("acme", EnvelopeParser, &stubParser{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	if !reg.HasHandler("acme", EnvelopeParser) {
		t.Fatal("HasHandler(acme, envelope_parser) = false, want true")
	}
}

func TestHasHandler_PackageLevel_DefaultRegistry(t *testing.T) {
	t.Cleanup(func() { resetDefaultRegistry(t) })
	if err := RegisterCapability("pkg-ns", SignatureVerifier, &stubVerifier{}); err != nil {
		t.Fatalf("RegisterCapability: %v", err)
	}
	if !HasHandler("pkg-ns", SignatureVerifier) {
		t.Fatal("package-level HasHandler = false after RegisterCapability, want true")
	}
}

func TestHasUnboundHandler_DistinguishesLegacyFromPackBoundProjection(t *testing.T) {
	reg := NewRegistry(gateInactive)
	if err := reg.UpsertCapability("legacy-ns", SignatureVerifier, &stubVerifier{}); err != nil {
		t.Fatalf("upsert legacy: %v", err)
	}
	if !reg.HasUnboundHandler("legacy-ns", SignatureVerifier) {
		t.Fatal("HasUnboundHandler(legacy-ns, signature_verifier) = false, want true")
	}

	if err := reg.UpsertCapabilityForPackInstall("pack-ns", SignatureVerifier, "pack-install-1", &stubVerifier{}); err != nil {
		t.Fatalf("upsert pack-bound: %v", err)
	}
	if !reg.HasHandler("pack-ns", SignatureVerifier) {
		t.Fatal("HasHandler(pack-ns, signature_verifier) = false, want true")
	}
	if reg.HasUnboundHandler("pack-ns", SignatureVerifier) {
		t.Fatal("HasUnboundHandler(pack-ns, signature_verifier) = true for pack-bound projection, want false")
	}
}

func TestBindLegacyHandlerToPackInstall_PinsLegacyProjectionWithoutMovingIt(t *testing.T) {
	reg := NewRegistry(gateActive)
	legacy := &stubVerifier{}
	if err := reg.UpsertCapability("legacy-ns", SignatureVerifier, legacy); err != nil {
		t.Fatalf("upsert legacy: %v", err)
	}
	if !reg.HasLegacyCompiledHandler("legacy-ns", SignatureVerifier) {
		t.Fatal("HasLegacyCompiledHandler(legacy-ns, signature_verifier) = false, want true")
	}
	if !reg.BindLegacyHandlerToPackInstall("legacy-ns", SignatureVerifier, "install-a") {
		t.Fatal("BindLegacyHandlerToPackInstall = false, want true")
	}
	if reg.HasUnboundHandler("legacy-ns", SignatureVerifier) {
		t.Fatal("HasUnboundHandler = true after binding, want false")
	}
	got, err := reg.ResolveSignatureVerifier(context.Background(), "legacy-ns", "install-a")
	if err != nil {
		t.Fatalf("resolve bound legacy projection: %v", err)
	}
	if got != legacy {
		t.Fatal("resolved handler is not the bound legacy projection")
	}
	if reg.BindLegacyHandlerToPackInstall("legacy-ns", SignatureVerifier, "install-b") {
		t.Fatal("BindLegacyHandlerToPackInstall moved legacy projection to a different install id")
	}

	if err := reg.UpsertCapabilityForPackInstall("pack-ns", SignatureVerifier, "install-a", &stubVerifier{}); err != nil {
		t.Fatalf("upsert pack-bound: %v", err)
	}
	if reg.HasLegacyCompiledHandler("pack-ns", SignatureVerifier) {
		t.Fatal("HasLegacyCompiledHandler(pack-ns, signature_verifier) = true for config-derived projection, want false")
	}
	if reg.BindLegacyHandlerToPackInstall("pack-ns", SignatureVerifier, "install-a") {
		t.Fatal("BindLegacyHandlerToPackInstall bound a config-derived projection")
	}
}

// TestHandlerErrorSentinels_ClientVsServerFaultContract pins the handler error
// taxonomy that the inbound webhook dispatcher (#338) relies on: ErrInvalidSignature
// and ErrMalformedPayload are the ONLY client-error sentinels (mapped to 401/400);
// they must be distinct, errors.Is-matchable when wrapped, and any other error
// (a server fault) must match neither.
func TestHandlerErrorSentinels_ClientVsServerFaultContract(t *testing.T) {
	if ErrInvalidSignature == nil || ErrMalformedPayload == nil {
		t.Fatal("handler-contract sentinels must be non-nil")
	}
	if errors.Is(ErrInvalidSignature, ErrMalformedPayload) || errors.Is(ErrMalformedPayload, ErrInvalidSignature) {
		t.Fatal("ErrInvalidSignature and ErrMalformedPayload must be distinct sentinels")
	}

	// Handlers wrap the sentinel with detail; errors.Is must still classify it.
	wrappedSig := fmt.Errorf("verify acme: %w", ErrInvalidSignature)
	if !errors.Is(wrappedSig, ErrInvalidSignature) {
		t.Fatal("wrapped ErrInvalidSignature must match via errors.Is")
	}
	if errors.Is(wrappedSig, ErrMalformedPayload) {
		t.Fatal("wrapped ErrInvalidSignature must not match ErrMalformedPayload")
	}
	wrappedParse := fmt.Errorf("parse acme: %w", ErrMalformedPayload)
	if !errors.Is(wrappedParse, ErrMalformedPayload) {
		t.Fatal("wrapped ErrMalformedPayload must match via errors.Is")
	}

	// A non-sentinel error (server fault) must match neither — the dispatcher
	// treats it as a retriable 5xx, not a terminal 401/400.
	serverFault := errors.New("connection refused")
	if errors.Is(serverFault, ErrInvalidSignature) || errors.Is(serverFault, ErrMalformedPayload) {
		t.Fatal("a non-sentinel server fault must not match either client-error sentinel")
	}
}

// --- Test: ResolveEnvelopeParser incarnation gate symmetric with ResolveSignatureVerifier ---

// TestResolveEnvelopeParser_IncarnationChanged_ReturnsErr mirrors the
// ResolveSignatureVerifier incarnation-change test: when the capability gate
// returns ErrCapabilityIncarnationChanged (pack reinstalled between Step 2 and
// Step 6), ResolveEnvelopeParser must propagate that sentinel so the dispatcher
// can map it to a retriable 503.
func TestResolveEnvelopeParser_IncarnationChanged_ReturnsErr(t *testing.T) {
	gate := gateError(ErrCapabilityIncarnationChanged)
	reg := NewRegistry(gate)
	if err := reg.RegisterEnvelopeParser("ns", &stubParser{}); err != nil {
		t.Fatalf("register: %v", err)
	}
	_, err := reg.ResolveEnvelopeParser(context.Background(), "ns", "install-a")
	if !errors.Is(err, ErrCapabilityIncarnationChanged) {
		t.Fatalf("expected ErrCapabilityIncarnationChanged, got %v", err)
	}
}

// TestResolveEnvelopeParser_EmptyPackInstallID_SkipsIncarnationCheck verifies
// the legacy path: passing "" as packInstallID skips the incarnation check
// (gate still controls namespace-level availability). This is symmetric with
// ResolveSignatureVerifier's "" path used by admin/test callers.
func TestResolveEnvelopeParser_EmptyPackInstallID_SkipsIncarnationCheck(t *testing.T) {
	// Gate always succeeds regardless of packInstallID (gateActive ignores it).
	reg := NewRegistry(gateActive)
	h := &stubParser{}
	if err := reg.RegisterEnvelopeParser("ns", h); err != nil {
		t.Fatalf("register: %v", err)
	}
	got, err := reg.ResolveEnvelopeParser(context.Background(), "ns", "")
	if err != nil {
		t.Fatalf("resolve with empty packInstallID: %v", err)
	}
	if got != h {
		t.Fatal("resolved handler is not the registered handler")
	}
}
