package service

import (
	"context"
	"net"
	"strings"
	"sync"
	"testing"
	"time"

	toki "git.toki-labs.com/toki/proto-socket/go"
	"google.golang.org/protobuf/proto"

	edgeevents "iop/apps/edge/internal/events"
	edgenode "iop/apps/edge/internal/node"
	"iop/packages/go/config"
	iop "iop/proto/gen/iop"
)

func TestRunHandleCloseIsIdempotent(t *testing.T) {
	calls := 0
	handle := &RunHandle{
		close: func() {
			calls++
		},
	}

	handle.Close()
	handle.Close()

	if calls != 1 {
		t.Fatalf("close called %d times, want 1", calls)
	}
}

func waitForCondition(t *testing.T, cond func() bool, msg string) {
	t.Helper()
	deadline := time.Now().Add(2 * time.Second)
	for time.Now().Before(deadline) {
		if cond() {
			return
		}
		time.Sleep(10 * time.Millisecond)
	}
	t.Fatal(msg)
}

func inflightRunCount(q *modelQueueManager) int {
	q.mu.Lock()
	defer q.mu.Unlock()
	return len(q.leaseByRun)
}

// TestRouteProviderTunnelFrameDoesNotPublishToBus verifies that tunnel frames
// reach only the request-bound tunnel subscriber and never the run event bus
// fanout (negative test for the passthrough/no-sideband boundary).
func TestRouteProviderTunnelFrameDoesNotPublishToBus(t *testing.T) {
	bus := edgeevents.NewBus()
	svc := New(edgenode.NewRegistry(), bus)

	runCh, unsubRun := bus.SubscribeAllRuns(8)
	defer unsubRun()

	tunnelCh, unsubTunnel := svc.tunnels.subscribe("tunnel-neg-1", 8)
	defer unsubTunnel()

	frame := &iop.ProviderTunnelFrame{
		RunId:    "run-neg-1",
		TunnelId: "tunnel-neg-1",
		Kind:     iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY,
		Body:     []byte("data: raw provider bytes\n\n"),
	}
	svc.RouteProviderTunnelFrame(frame)

	select {
	case got := <-tunnelCh:
		if string(got.GetBody()) != string(frame.GetBody()) {
			t.Fatalf("tunnel frame body mismatch: %q", got.GetBody())
		}
	case <-time.After(time.Second):
		t.Fatal("tunnel subscriber did not receive the frame")
	}

	select {
	case e := <-runCh:
		t.Fatalf("tunnel frame leaked into run event bus: %+v", e)
	case <-time.After(100 * time.Millisecond):
	}
}

// TestRouteProviderTunnelFrameWithoutSubscriberIsDropped verifies routing a
// frame with no subscriber neither blocks nor panics.
func TestRouteProviderTunnelFrameWithoutSubscriberIsDropped(t *testing.T) {
	svc := New(edgenode.NewRegistry(), nil)
	svc.RouteProviderTunnelFrame(&iop.ProviderTunnelFrame{TunnelId: "no-sub"})
}

type providerTunnelTestEnv struct {
	svc        *Service
	nodeClient *toki.TcpClient
	capturedMu sync.Mutex
	captured   *iop.ProviderTunnelRequest
}

func (e *providerTunnelTestEnv) capturedRequest() *iop.ProviderTunnelRequest {
	e.capturedMu.Lock()
	defer e.capturedMu.Unlock()
	return e.captured
}

// newProviderTunnelTestEnv wires a provider-pool service to a fake node over
// net.Pipe and captures the ProviderTunnelRequest the node receives.
func newProviderTunnelTestEnv(t *testing.T) *providerTunnelTestEnv {
	t.Helper()
	edgeConn, nodeConn := net.Pipe()
	t.Cleanup(func() {
		_ = edgeConn.Close()
		_ = nodeConn.Close()
	})

	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.ProviderTunnelRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.ProviderTunnelRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}
	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)

	env := &providerTunnelTestEnv{nodeClient: nodeClient}
	toki.AddListenerTyped[*iop.ProviderTunnelRequest](&nodeClient.Communicator, func(req *iop.ProviderTunnelRequest) {
		env.capturedMu.Lock()
		env.captured = req
		env.capturedMu.Unlock()
	})

	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID:      "node-pool",
		Runtime: config.RuntimeConf{Concurrency: 4},
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{
				ID:                     "prov-vllm-01",
				Adapter:                "vllm-gpu",
				Type:                   "vllm",
				Models:                 []string{"served-qwen"},
				Health:                 "available",
				Capacity:               1,
				ResponseStallTimeoutMS: 45000,
			},
		},
	})

	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{
		NodeID:                       "node-pool",
		LifecycleState:               edgenode.LifecycleConnected,
		Client:                       edgeClient,
		CredentialRecipientKeyID:     "recipient-pool",
		CredentialRecipientPublicKey: make([]byte, 32),
	})

	svc := New(reg, edgeevents.NewBus())
	svc.SetNodeStore(store)
	svc.SetModelCatalog([]config.ModelCatalogEntry{
		{ID: "qwen3.6:35b", Providers: map[string]string{"prov-vllm-01": "served-qwen"}},
	})
	env.svc = svc
	return env
}

// TestSubmitProviderTunnelProviderPoolSendsRequestAndReleasesSlotOnEnd covers
// the provider-pool tunnel path end to end: admission rewrites adapter/target,
// BuildBody receives the served target, ordered frames reach the handle, and
// the admission slot is released on the END frame.
func TestSubmitProviderTunnelProviderPoolSendsRequestAndReleasesSlotOnEnd(t *testing.T) {
	env := newProviderTunnelTestEnv(t)
	svc := env.svc

	var builtTarget string
	handle, err := svc.SubmitProviderTunnel(context.Background(), SubmitProviderTunnelRequest{
		RunID:         "run-tunnel-001",
		ModelGroupKey: "qwen3.6:35b",
		Method:        "POST",
		Path:          "/v1/chat/completions",
		BuildBody: func(target string) ([]byte, error) {
			builtTarget = target
			return []byte(`{"model":"` + target + `","stream":true}`), nil
		},
		Stream:       true,
		ProviderPool: true,
	})
	if err != nil {
		t.Fatalf("SubmitProviderTunnel: %v", err)
	}
	defer handle.Close()

	if builtTarget != "served-qwen" {
		t.Errorf("BuildBody target: got %q, want served-qwen", builtTarget)
	}
	dispatch := handle.Dispatch()
	if dispatch.Adapter != "vllm-gpu" || dispatch.Target != "served-qwen" {
		t.Errorf("dispatch adapter/target: got %q/%q", dispatch.Adapter, dispatch.Target)
	}

	waitForCondition(t, func() bool { return env.capturedRequest() != nil },
		"fake node did not receive ProviderTunnelRequest")
	captured := env.capturedRequest()
	if captured.GetAdapter() != "vllm-gpu" || captured.GetTarget() != "served-qwen" || captured.GetResponseStallTimeoutMs() != 45000 {
		t.Errorf("wire adapter/target: got %q/%q", captured.GetAdapter(), captured.GetTarget())
	}
	if !strings.Contains(string(captured.GetBody()), `"model":"served-qwen"`) {
		t.Errorf("wire body missing served model: %s", captured.GetBody())
	}
	if inflightRunCount(svc.queue) != 1 {
		t.Fatalf("expected 1 inflight run after dispatch, got %d", inflightRunCount(svc.queue))
	}

	tunnelID := captured.GetTunnelId()
	frames := []*iop.ProviderTunnelFrame{
		{RunId: captured.GetRunId(), TunnelId: tunnelID, Sequence: 0, Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200},
		{RunId: captured.GetRunId(), TunnelId: tunnelID, Sequence: 1, Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: chunk-1\n\n")},
		{RunId: captured.GetRunId(), TunnelId: tunnelID, Sequence: 2, Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true},
	}
	for _, f := range frames {
		svc.RouteProviderTunnelFrame(f)
	}

	stream := handle.Stream()
	for i, want := range frames {
		select {
		case got := <-stream.Frames:
			if got.GetSequence() != want.GetSequence() || got.GetKind() != want.GetKind() {
				t.Fatalf("frame %d: got seq=%d kind=%v", i, got.GetSequence(), got.GetKind())
			}
		case <-time.After(time.Second):
			t.Fatalf("frame %d not delivered", i)
		}
	}
	select {
	case _, open := <-stream.Frames:
		if open {
			t.Fatal("expected frame channel to close after END")
		}
	case <-time.After(time.Second):
		t.Fatal("frame channel not closed after END")
	}

	waitForCondition(t, func() bool { return inflightRunCount(svc.queue) == 0 },
		"admission slot not released after END frame")
}

// TestSubmitProviderTunnelCloseReleasesSlot verifies the cancel path: closing
// the handle before a terminal frame releases the provider-pool slot.
func TestSubmitProviderTunnelCloseReleasesSlot(t *testing.T) {
	env := newProviderTunnelTestEnv(t)
	svc := env.svc

	handle, err := svc.SubmitProviderTunnel(context.Background(), SubmitProviderTunnelRequest{
		RunID:         "run-tunnel-cancel",
		ModelGroupKey: "qwen3.6:35b",
		Method:        "POST",
		Path:          "/v1/chat/completions",
		Body:          []byte(`{"model":"qwen3.6:35b"}`),
		ProviderPool:  true,
	})
	if err != nil {
		t.Fatalf("SubmitProviderTunnel: %v", err)
	}
	if inflightRunCount(svc.queue) != 1 {
		t.Fatalf("expected 1 inflight run after dispatch, got %d", inflightRunCount(svc.queue))
	}

	handle.Close()

	waitForCondition(t, func() bool { return inflightRunCount(svc.queue) == 0 },
		"admission slot not released after Close")
}

// TestProviderTunnelRouterBackpressureBlocksWithoutDrop verifies the router
// blocks the producer while the subscriber buffer is full instead of dropping
// frames, and delivers every frame in order once the subscriber drains.
func TestProviderTunnelRouterBackpressureBlocksWithoutDrop(t *testing.T) {
	svc := New(edgenode.NewRegistry(), nil)
	ch, unsub := svc.tunnels.subscribe("tunnel-bp", 2)
	defer unsub()

	const total = 6
	routed := make(chan struct{})
	go func() {
		defer close(routed)
		for i := 0; i < total; i++ {
			svc.tunnels.route(&iop.ProviderTunnelFrame{
				TunnelId: "tunnel-bp",
				Sequence: int64(i),
				Kind:     iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY,
			})
		}
	}()

	// With buffer 2 the producer cannot finish all routes before the
	// subscriber drains: full buffer must block, not drop.
	select {
	case <-routed:
		t.Fatal("route calls finished while the subscriber buffer was full; expected blocking backpressure")
	case <-time.After(100 * time.Millisecond):
	}

	for i := 0; i < total; i++ {
		select {
		case f := <-ch:
			if f.GetSequence() != int64(i) {
				t.Fatalf("frame %d: got sequence %d; frames must stay ordered and lossless under backpressure", i, f.GetSequence())
			}
		case <-time.After(time.Second):
			t.Fatalf("frame %d not delivered after draining", i)
		}
	}

	select {
	case <-routed:
	case <-time.After(time.Second):
		t.Fatal("producer still blocked after all frames were drained")
	}
}

// TestProviderTunnelRouterCloseUnblocksBlockedRoute verifies unsubscribe (the
// handle Close/cancel path) releases a route call blocked on a full buffer, so
// an abandoned tunnel can never wedge the transport frame reader.
func TestProviderTunnelRouterCloseUnblocksBlockedRoute(t *testing.T) {
	svc := New(edgenode.NewRegistry(), nil)
	_, unsub := svc.tunnels.subscribe("tunnel-close", 1)

	frame := func(seq int64) *iop.ProviderTunnelFrame {
		return &iop.ProviderTunnelFrame{
			TunnelId: "tunnel-close",
			Sequence: seq,
			Kind:     iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY,
		}
	}

	blocked := make(chan struct{})
	go func() {
		defer close(blocked)
		svc.tunnels.route(frame(0)) // fills the buffer
		svc.tunnels.route(frame(1)) // blocks until close releases it
	}()

	select {
	case <-blocked:
		t.Fatal("second route must block on the full subscriber buffer")
	case <-time.After(100 * time.Millisecond):
	}

	unsub()

	select {
	case <-blocked:
	case <-time.After(time.Second):
		t.Fatal("unsubscribe did not release the blocked route call")
	}

	// A late frame for the closed tunnel is dropped without blocking.
	svc.tunnels.route(frame(2))
}

// TestProviderTunnelReleaseExactlyOnceAcrossEndAndClose verifies the tracked
// provider-pool slot (capacity 1) is released exactly once when both the
// terminal END frame and handle Close fire, and that the freed slot admits the
// next tunnel immediately instead of queueing.
func TestProviderTunnelReleaseExactlyOnceAcrossEndAndClose(t *testing.T) {
	env := newProviderTunnelTestEnv(t)
	svc := env.svc

	submit := func(runID string) ProviderTunnelResult {
		t.Helper()
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		handle, err := svc.SubmitProviderTunnel(ctx, SubmitProviderTunnelRequest{
			RunID:         runID,
			ModelGroupKey: "qwen3.6:35b",
			Method:        "POST",
			Path:          "/v1/chat/completions",
			Body:          []byte(`{"model":"qwen3.6:35b"}`),
			ProviderPool:  true,
		})
		if err != nil {
			t.Fatalf("SubmitProviderTunnel(%s): %v", runID, err)
		}
		return handle
	}

	handle1 := submit("run-release-1")
	waitForCondition(t, func() bool {
		captured := env.capturedRequest()
		return captured != nil && captured.GetRunId() == "run-release-1"
	}, "fake node did not receive the first ProviderTunnelRequest")
	captured := env.capturedRequest()

	svc.RouteProviderTunnelFrame(&iop.ProviderTunnelFrame{
		RunId:    captured.GetRunId(),
		TunnelId: captured.GetTunnelId(),
		Kind:     iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END,
		End:      true,
	})
	stream := handle1.Stream()
	for {
		if _, open := <-stream.Frames; !open {
			break
		}
	}
	waitForCondition(t, func() bool { return inflightRunCount(svc.queue) == 0 },
		"admission slot not released after END frame")
	if got := leaseCount(svc.queue); got != 0 {
		t.Fatalf("live leases after END frame: got %d, want 0", got)
	}

	// Close after the terminal frame must stay a no-op release: the lease it
	// would target is already gone, so there is nothing left to decrement.
	handle1.Close()
	if got := inflightRunCount(svc.queue); got != 0 {
		t.Fatalf("inflight count after END+Close: got %d, want 0", got)
	}
	if got := leaseCount(svc.queue); got != 0 {
		t.Fatalf("live leases after END+Close: got %d, want 0", got)
	}

	// The capacity-1 slot is free again: the next tunnel is admitted without
	// waiting on the queue (a leaked slot would block this submit).
	handle2 := submit("run-release-2")
	defer handle2.Close()
	waitForCondition(t, func() bool {
		captured := env.capturedRequest()
		return captured != nil && captured.GetRunId() == "run-release-2"
	}, "fake node did not receive the second ProviderTunnelRequest after slot release")
	if got := inflightRunCount(svc.queue); got != 1 {
		t.Fatalf("inflight count after second dispatch: got %d, want 1", got)
	}
	// The second tunnel holds its own lease identity, so a late release carrying
	// the first tunnel's id can never free it.
	if got := leaseCount(svc.queue); got != 1 {
		t.Fatalf("live leases after second dispatch: got %d, want 1", got)
	}
}

// TestSubmitProviderTunnelErrorFrameReleasesSlot verifies the ERROR frame is
// terminal and releases the provider-pool slot like END.
func TestSubmitProviderTunnelErrorFrameReleasesSlot(t *testing.T) {
	env := newProviderTunnelTestEnv(t)
	svc := env.svc

	handle, err := svc.SubmitProviderTunnel(context.Background(), SubmitProviderTunnelRequest{
		RunID:         "run-tunnel-error",
		ModelGroupKey: "qwen3.6:35b",
		Method:        "POST",
		Path:          "/v1/chat/completions",
		Body:          []byte(`{"model":"qwen3.6:35b"}`),
		ProviderPool:  true,
	})
	if err != nil {
		t.Fatalf("SubmitProviderTunnel: %v", err)
	}
	defer handle.Close()

	waitForCondition(t, func() bool { return env.capturedRequest() != nil },
		"fake node did not receive ProviderTunnelRequest")
	captured := env.capturedRequest()

	svc.RouteProviderTunnelFrame(&iop.ProviderTunnelFrame{
		RunId:    captured.GetRunId(),
		TunnelId: captured.GetTunnelId(),
		Kind:     iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_ERROR,
		Error:    "provider unavailable",
	})

	stream := handle.Stream()
	select {
	case got := <-stream.Frames:
		if got.GetKind() != iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_ERROR {
			t.Fatalf("expected ERROR frame, got %v", got.GetKind())
		}
	case <-time.After(time.Second):
		t.Fatal("ERROR frame not delivered")
	}

	waitForCondition(t, func() bool { return inflightRunCount(svc.queue) == 0 },
		"admission slot not released after ERROR frame")
}

// TestSubmitProviderPoolSelectsTunnelProviderAndReleasesSlot verifies that
// SubmitProviderPool with an OpenAI-compatible provider (tunnel path) performs
// a single admission, sends a ProviderTunnelRequest, and releases the slot on
// tunnel END frame.
func TestSubmitProviderPoolSelectsTunnelProviderAndReleasesSlot(t *testing.T) {
	env := newProviderTunnelTestEnv(t)
	svc := env.svc

	// Catalog entry with a vLLM provider (tunnel path).
	catalog := []config.ModelCatalogEntry{
		{ID: "qwen3.6:35b", Providers: map[string]string{"prov-vllm-01": "served-qwen"}},
	}
	svc.SetModelCatalog(catalog)

	result, err := svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
		Run: SubmitRunRequest{
			ModelGroupKey: "qwen3.6:35b",
			ProviderPool:  true,
			Background:    true,
		},
	})
	if err != nil {
		t.Fatalf("SubmitProviderPool: %v", err)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if result.Path != ProviderPoolPathTunnel {
		t.Fatalf("expected path=tunnel, got %q", result.Path)
	}
	if result.Tunnel == nil {
		t.Fatal("expected tunnel handle for tunnel path")
	}
	if result.Run != nil {
		t.Fatal("expected nil run for tunnel path")
	}

	// Verify inflight after dispatch.
	if got := inflightRunCount(svc.queue); got != 1 {
		t.Fatalf("expected 1 inflight after dispatch, got %d", got)
	}
}

// TestSubmitProviderPoolSelectsNormalizedProviderAndDoesNotOpenTunnel verifies
// that SubmitProviderPool with an Ollama provider (normalized path) performs
// a single admission, sends a RunRequest (not ProviderTunnelRequest), and
// the tunnel handle is nil.
func TestSubmitProviderPoolSelectsNormalizedProviderAndDoesNotOpenTunnel(t *testing.T) {
	// Build a provider-pool service with an Ollama provider.
	edgeConn, nodeConn := net.Pipe()
	t.Cleanup(func() {
		_ = edgeConn.Close()
		_ = nodeConn.Close()
	})

	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)

	// Capture the RunRequest received by the fake node.
	var capturedRunReq *iop.RunRequest
	var capturedMu sync.Mutex
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(req *iop.RunRequest) {
		capturedMu.Lock()
		capturedRunReq = req
		capturedMu.Unlock()
	})

	// Build NodeStore with an Ollama provider (normalized path).
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID:      "node-ollama",
		Runtime: config.RuntimeConf{Concurrency: 2},
		Adapters: config.AdaptersConf{
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
			},
		},
		Providers: []config.NodeProviderConf{
			{
				ID:       "prov-ollama-01",
				Adapter:  "ollama",
				Type:     "ollama",
				Models:   []string{"served-qwen"},
				Health:   "available",
				Capacity: 2,
			},
		},
	})

	// Build catalog with Ollama provider.
	catalog := []config.ModelCatalogEntry{
		{ID: "qwen3.6:35b", Providers: map[string]string{"prov-ollama-01": "served-qwen"}},
	}

	// Build registry with the fake node.
	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{
		NodeID:         "node-ollama",
		LifecycleState: edgenode.LifecycleConnected,
		Client:         edgeClient,
	})

	// Create Service with queue and catalog.
	bus := edgeevents.NewBus()
	svc := New(reg, bus)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	// SubmitProviderPool with Ollama provider → normalized path.
	result, err := svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
		Run: SubmitRunRequest{
			ModelGroupKey: "qwen3.6:35b",
			ProviderPool:  true,
			Background:    true,
		},
	})
	if err != nil {
		t.Fatalf("SubmitProviderPool: %v", err)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}
	if result.Path != ProviderPoolPathNormalized {
		t.Fatalf("expected path=normalized, got %q", result.Path)
	}
	if result.Run == nil {
		t.Fatal("expected run handle for normalized path")
	}
	if result.Tunnel != nil {
		t.Fatal("expected nil tunnel for normalized path")
	}

	// Wait for the fake node to receive the RunRequest.
	time.Sleep(50 * time.Millisecond)

	capturedMu.Lock()
	defer capturedMu.Unlock()

	if capturedRunReq == nil {
		t.Fatal("no RunRequest captured from fake node; SubmitProviderPool did not send")
	}

	// Verify that the adapter and target were rewritten from the provider-pool candidate.
	if capturedRunReq.GetAdapter() != "ollama" {
		t.Errorf("adapter: got %q, want %q", capturedRunReq.GetAdapter(), "ollama")
	}
	if capturedRunReq.GetTarget() != "served-qwen" {
		t.Errorf("target: got %q, want %q", capturedRunReq.GetTarget(), "served-qwen")
	}

	// Verify inflight after dispatch.
	if got := inflightRunCount(svc.queue); got != 1 {
		t.Fatalf("expected 1 inflight after dispatch, got %d", got)
	}
}

// TestSubmitProviderPoolUsesSingleAdmissionForMixedCandidates verifies that
// SubmitProviderPool performs exactly one queue admission when presented with
// mixed tunnel+normalized candidates (e.g., vLLM + Ollama). The selected
// candidate (by in-flight/priority rotation) determines the path.
func TestSubmitProviderPoolUsesSingleAdmissionForMixedCandidates(t *testing.T) {
	edgeConn, nodeConn := net.Pipe()
	t.Cleanup(func() {
		_ = edgeConn.Close()
		_ = nodeConn.Close()
	})

	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)

	var capturedRunReq *iop.RunRequest
	var capturedMu sync.Mutex
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(req *iop.RunRequest) {
		capturedMu.Lock()
		capturedRunReq = req
		capturedMu.Unlock()
	})

	// Build NodeStore with two providers: vLLM (tunnel) and Ollama (normalized).
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID:      "node-mixed",
		Runtime: config.RuntimeConf{Concurrency: 4},
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
			},
		},
		Providers: []config.NodeProviderConf{
			{
				ID:       "prov-vllm",
				Adapter:  "vllm-gpu",
				Type:     "vllm",
				Models:   []string{"served-model"},
				Health:   "available",
				Capacity: 2,
			},
			{
				ID:       "prov-ollama",
				Adapter:  "ollama",
				Type:     "ollama",
				Models:   []string{"served-model"},
				Health:   "available",
				Capacity: 2,
			},
		},
	})

	// Catalog with both providers.
	catalog := []config.ModelCatalogEntry{
		{ID: "mixed-model", Providers: map[string]string{"prov-vllm": "served-model", "prov-ollama": "served-model"}},
	}

	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{
		NodeID:         "node-mixed",
		LifecycleState: edgenode.LifecycleConnected,
		Client:         edgeClient,
	})

	bus := edgeevents.NewBus()
	svc := New(reg, bus)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	// SubmitProviderPool → should do exactly one admission.
	result, err := svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
		Run: SubmitRunRequest{
			ModelGroupKey: "mixed-model",
			ProviderPool:  true,
			Background:    true,
		},
	})
	if err != nil {
		t.Fatalf("SubmitProviderPool: %v", err)
	}
	if result == nil {
		t.Fatal("expected non-nil result")
	}

	// The selected provider depends on admission order (vLLM or Ollama by
	// providerID alphabetical tie-break). Verify only one request was sent.
	time.Sleep(50 * time.Millisecond)

	capturedMu.Lock()
	defer capturedMu.Unlock()

	if capturedRunReq == nil {
		t.Fatal("expected exactly one RunRequest for normalized path; admission must be single")
	}

	// Verify the request target matches the catalog served model.
	if capturedRunReq.GetTarget() != "served-model" {
		t.Errorf("target: got %q, want %q", capturedRunReq.GetTarget(), "served-model")
	}

	// Verify inflight is exactly 1 (single admission).
	if got := inflightRunCount(svc.queue); got != 1 {
		t.Fatalf("expected 1 inflight (single admission), got %d", got)
	}
}

// TestDispatchRejectsStaleGenerationBeforeSend pins the pre-send generation
// fence on both execution paths: when the connection a lease was admitted under
// is superseded by a reconnect before dispatch, the normalized path releases the
// lease without ever sending a RunRequest, and the tunnel path releases it
// without ever sending a ProviderTunnelRequest, to the stale (dead) client. Both
// request parsers are wired to the stale connection so a leaked send of either
// type is caught directly.
func TestDispatchRejectsStaleGenerationBeforeSend(t *testing.T) {
	t.Run("normalized", func(t *testing.T) {
		staleGenerationFenceCase(t, providerExecutionPathNormalized)
	})
	t.Run("tunnel", func(t *testing.T) {
		staleGenerationFenceCase(t, providerExecutionPathTunnel)
	})
}

// staleGenerationFenceCase drives one execution path through its provider-pool
// dispatch entry point with a lease admitted under a connection a reconnect has
// already superseded, and asserts the fence releases the lease and fails with no
// request of any type reaching the stale connection.
func staleGenerationFenceCase(t *testing.T, path providerExecutionPath) {
	t.Helper()

	edgeConn, nodeConn := net.Pipe()
	t.Cleanup(func() {
		_ = edgeConn.Close()
		_ = nodeConn.Close()
	})

	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
		toki.TypeNameOf(&iop.ProviderTunnelRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.ProviderTunnelRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}
	staleClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)

	var capturedMu sync.Mutex
	captured := 0
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(*iop.RunRequest) {
		capturedMu.Lock()
		captured++
		capturedMu.Unlock()
	})
	toki.AddListenerTyped[*iop.ProviderTunnelRequest](&nodeClient.Communicator, func(*iop.ProviderTunnelRequest) {
		capturedMu.Lock()
		captured++
		capturedMu.Unlock()
	})

	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID:      "node-stale",
		Runtime: config.RuntimeConf{Concurrency: 1},
		Adapters: config.AdaptersConf{
			OllamaInstances: []config.OllamaInstanceConf{{Name: "ollama", Enabled: true}},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-stale", Adapter: "ollama", Type: "ollama", Models: []string{"served"}, Health: "available", Capacity: 1},
		},
	})

	reg := edgenode.NewRegistry()
	// entry1 is the connection admission selected; it holds the stale client.
	entry1 := &edgenode.NodeEntry{NodeID: "node-stale", LifecycleState: edgenode.LifecycleConnected, Client: staleClient}
	reg.Register(entry1)
	staleGeneration := entry1.ConnectionGeneration
	// A reconnect supersedes entry1: the registry's current owner is a strictly
	// higher generation, so entry1's lease is now stale.
	reg.Register(&edgenode.NodeEntry{NodeID: "node-stale", LifecycleState: edgenode.LifecycleConnected, Client: &toki.TcpClient{}})
	if reg.IsCurrentOwnerGeneration("node-stale", staleGeneration) {
		t.Fatal("reconnect should have superseded the stale generation")
	}

	svc := New(reg, edgeevents.NewBus())
	svc.SetNodeStore(store)
	svc.SetModelCatalog([]config.ModelCatalogEntry{
		{ID: "stale-model", Providers: map[string]string{"prov-stale": "served"}},
	})

	// Admit a lease bound to entry1's (now stale) generation on the path under test.
	selectedCand := candidateNode{
		entry:         entry1,
		providerID:    "prov-stale",
		servedTarget:  "served",
		generation:    staleGeneration,
		capacity:      1,
		adapter:       "ollama",
		providerType:  "ollama",
		executionPath: path,
	}
	admitted, _, err := svc.queue.admitWithReason(context.Background(), "stale-model", "ollama", "served", []candidateNode{selectedCand}, groupPolicy{}, nil, false, false)
	if err != nil || admitted == nil {
		t.Fatalf("admit: %v", err)
	}
	if leaseCount(svc.queue) != 1 {
		t.Fatalf("expected 1 live lease after admit, got %d", leaseCount(svc.queue))
	}
	reservation := newQueueReservation(svc.queue, admitted)

	switch path {
	case providerExecutionPathNormalized:
		req := SubmitRunRequest{ModelGroupKey: "stale-model", ProviderPool: true, Background: true}
		result, err := svc.dispatchProviderPoolRun(context.Background(), req, "ollama", "served", admitted, "dispatched", reservation)
		if err == nil {
			t.Fatal("expected stale-generation dispatch to be rejected before send")
		}
		if result != nil {
			t.Fatal("expected nil result on a fenced dispatch")
		}
		if !strings.Contains(err.Error(), "connection changed before dispatch") {
			t.Fatalf("unexpected error: %v", err)
		}
	case providerExecutionPathTunnel:
		req := ProviderPoolDispatchRequest{
			Run: SubmitRunRequest{ModelGroupKey: "stale-model", ProviderPool: true},
			Tunnel: SubmitProviderTunnelRequest{
				ModelGroupKey: "stale-model",
				ProviderPool:  true,
				Method:        "POST",
				Path:          "/v1/chat/completions",
			},
		}
		result, err := svc.dispatchProviderPoolTunnel(context.Background(), req, "ollama", "served", admitted, "dispatched", reservation)
		if err == nil {
			t.Fatal("expected stale-generation tunnel dispatch to be rejected before send")
		}
		if result != nil {
			t.Fatal("expected nil result on a fenced tunnel dispatch")
		}
		if !strings.Contains(err.Error(), "connection changed before dispatch") {
			t.Fatalf("unexpected error: %v", err)
		}
	default:
		t.Fatalf("unhandled execution path %q", path)
	}

	// The fenced dispatch must have released its lease and never sent a request.
	if got := leaseCount(svc.queue); got != 0 {
		t.Fatalf("expected the fenced dispatch to release its lease, got %d live leases", got)
	}
	time.Sleep(50 * time.Millisecond)
	capturedMu.Lock()
	sent := captured
	capturedMu.Unlock()
	if sent != 0 {
		t.Fatalf("a request reached the stale connection %d time(s); want none", sent)
	}
}

// TestSubmitProviderPoolDispatchInfoObservation verifies provider-pool dispatch facts.
func TestSubmitProviderPoolDispatchInfoObservation(t *testing.T) {
	for _, tc := range []struct {
		name         string
		providerType string
		wantPath     providerPoolPath
		wantExecPath string
	}{
		{
			name:         "vllm_tunnel",
			providerType: "vllm",
			wantPath:     ProviderPoolPathTunnel,
			wantExecPath: "provider_tunnel",
		},
		{
			name:         "ollama_normalized",
			providerType: "ollama",
			wantPath:     ProviderPoolPathNormalized,
			wantExecPath: "normalized",
		},
		{
			name:         "cli_normalized",
			providerType: "cli",
			wantPath:     ProviderPoolPathNormalized,
			wantExecPath: "normalized",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			edgeConn, nodeConn := net.Pipe()
			t.Cleanup(func() {
				_ = edgeConn.Close()
				_ = nodeConn.Close()
			})

			parserMap := toki.ParserMap{
				toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
					m := &iop.RunRequest{}
					return m, proto.Unmarshal(b, m)
				},
				toki.TypeNameOf(&iop.ProviderTunnelRequest{}): func(b []byte) (proto.Message, error) {
					m := &iop.ProviderTunnelRequest{}
					return m, proto.Unmarshal(b, m)
				},
			}

			edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
			_ = toki.NewTcpClient(nodeConn, 0, 0, parserMap)

			store := edgenode.NewNodeStore()
			store.Add(&edgenode.NodeRecord{
				ID:      "node-obs",
				Runtime: config.RuntimeConf{Concurrency: 2},
				Adapters: config.AdaptersConf{
					VllmInstances:         []config.VllmInstanceConf{{Name: "vllm-gpu", Enabled: true}},
					OllamaInstances:       []config.OllamaInstanceConf{{Name: "ollama", Enabled: true}},
					OpenAICompatInstances: []config.OpenAICompatInstanceConf{{Name: "openai", Enabled: true}},
				},
				Providers: []config.NodeProviderConf{
					{
						ID:       "prov-obs",
						Adapter:  "vllm-gpu",
						Type:     tc.providerType,
						Models:   []string{"served-model"},
						Health:   "available",
						Capacity: 2,
					},
				},
			})

			catalog := []config.ModelCatalogEntry{
				{ID: "obs-model", Providers: map[string]string{"prov-obs": "served-model"}},
			}

			reg := edgenode.NewRegistry()
			reg.Register(&edgenode.NodeEntry{
				NodeID:         "node-obs",
				LifecycleState: edgenode.LifecycleConnected,
				Client:         edgeClient,
			})

			bus := edgeevents.NewBus()
			svc := New(reg, bus)
			svc.SetNodeStore(store)
			svc.SetModelCatalog(catalog)

			result, err := svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
				Run: SubmitRunRequest{
					ModelGroupKey: "obs-model",
					ProviderPool:  true,
					Background:    true,
				},
			})
			if err != nil {
				t.Fatalf("SubmitProviderPool: %v", err)
			}
			if result == nil {
				t.Fatal("expected non-nil result")
			}
			if result.Path != tc.wantPath {
				t.Fatalf("path: got %q, want %q", result.Path, tc.wantPath)
			}

			disp := result.DispatchInfo
			if disp.ProviderID != "prov-obs" {
				t.Errorf("provider_id: got %q, want %q", disp.ProviderID, "prov-obs")
			}
			if disp.ProviderType != tc.providerType {
				t.Errorf("provider_type: got %q, want %q", disp.ProviderType, tc.providerType)
			}
			if disp.ExecutionPath != tc.wantExecPath {
				t.Errorf("execution_path: got %q, want %q", disp.ExecutionPath, tc.wantExecPath)
			}
			if disp.QueueReason != "dispatched" {
				t.Errorf("queue_reason: got %q, want %q", disp.QueueReason, "dispatched")
			}
			if disp.Adapter != "vllm-gpu" {
				t.Errorf("adapter: got %q, want %q", disp.Adapter, "vllm-gpu")
			}
			if disp.Target != "served-model" {
				t.Errorf("target: got %q, want %q", disp.Target, "served-model")
			}
			// Also verify tunnel/normalized handle DispatchInfo matches.
			switch result.Path {
			case ProviderPoolPathTunnel:
				if result.Tunnel == nil {
					t.Fatal("expected tunnel handle for tunnel path")
				}
				hDisp := result.Tunnel.Dispatch()
				if hDisp.ProviderID != "prov-obs" {
					t.Errorf("tunnel handle provider_id: got %q, want %q", hDisp.ProviderID, "prov-obs")
				}
				if hDisp.ProviderType != tc.providerType {
					t.Errorf("tunnel handle provider_type: got %q, want %q", hDisp.ProviderType, tc.providerType)
				}
				if hDisp.ExecutionPath != tc.wantExecPath {
					t.Errorf("tunnel handle execution_path: got %q, want %q", hDisp.ExecutionPath, tc.wantExecPath)
				}
				// Close releases the inflight slot.
				result.Tunnel.Close()
				if got := inflightRunCount(svc.queue); got != 0 {
					t.Fatalf("expected 0 inflight after tunnel close, got %d", got)
				}
			case ProviderPoolPathNormalized:
				if result.Run == nil {
					t.Fatal("expected run handle for normalized path")
				}
				hDisp := result.Run.Dispatch()
				if hDisp.ProviderID != "prov-obs" {
					t.Errorf("run handle provider_id: got %q, want %q", hDisp.ProviderID, "prov-obs")
				}
				if hDisp.ProviderType != tc.providerType {
					t.Errorf("run handle provider_type: got %q, want %q", hDisp.ProviderType, tc.providerType)
				}
				if hDisp.ExecutionPath != tc.wantExecPath {
					t.Errorf("run handle execution_path: got %q, want %q", hDisp.ExecutionPath, tc.wantExecPath)
				}
				// Inflight should be 1 for normalized (run hasn't completed yet).
				if got := inflightRunCount(svc.queue); got != 1 {
					t.Fatalf("expected 1 inflight for normalized, got %d", got)
				}
				result.Run.Close()
			}
		})
	}
}

// TestPendingClientDirectSendFence asserts the pending→ready dispatch fence at
// the granularity of the exact protobuf each direct wire carries. While the
// connection is pending every direct send path fails and nothing reaches the
// node. After the ready transition each wire is delivered exactly once, and its
// identity fields — not merely its type — are asserted against a cloned copy of
// the received message, so a request that dispatched with an empty or wrong field
// can no longer pass by type count alone.
func TestPendingClientDirectSendFence(t *testing.T) {
	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer nodeConn.Close()

	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.NodeCommandRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.NodeCommandRequest{}
			return m, proto.Unmarshal(b, m)
		},
		toki.TypeNameOf(&iop.NodeCommandResponse{}): func(b []byte) (proto.Message, error) {
			m := &iop.NodeCommandResponse{}
			return m, proto.Unmarshal(b, m)
		},
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
		toki.TypeNameOf(&iop.CancelRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.CancelRequest{}
			return m, proto.Unmarshal(b, m)
		},
		toki.TypeNameOf(&iop.ProviderTunnelRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.ProviderTunnelRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)

	// Each listener clones the received protobuf so its exact identity fields can
	// be asserted; fire-and-forget wires arrive on buffered channels, and the
	// synchronous NodeCommandRequests accumulate under a lock keyed by type.
	runReqs := make(chan *iop.RunRequest, 4)
	tunnelReqs := make(chan *iop.ProviderTunnelRequest, 4)
	cancelReqs := make(chan *iop.CancelRequest, 4)
	var cmdMu sync.Mutex
	cmdReqs := make(map[iop.NodeCommandType][]*iop.NodeCommandRequest)

	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(req *iop.RunRequest) {
		runReqs <- proto.Clone(req).(*iop.RunRequest)
	})
	toki.AddListenerTyped[*iop.ProviderTunnelRequest](&nodeClient.Communicator, func(req *iop.ProviderTunnelRequest) {
		tunnelReqs <- proto.Clone(req).(*iop.ProviderTunnelRequest)
	})
	toki.AddListenerTyped[*iop.CancelRequest](&nodeClient.Communicator, func(req *iop.CancelRequest) {
		cancelReqs <- proto.Clone(req).(*iop.CancelRequest)
	})
	toki.AddRequestListenerTyped(&nodeClient.Communicator, func(req *iop.NodeCommandRequest) (*iop.NodeCommandResponse, error) {
		cmdMu.Lock()
		cmdReqs[req.GetType()] = append(cmdReqs[req.GetType()], proto.Clone(req).(*iop.NodeCommandRequest))
		cmdMu.Unlock()
		return &iop.NodeCommandResponse{
			RequestId: req.GetRequestId(),
			Type:      req.GetType(),
			Result:    map[string]string{"result": "ok", "status_code": "200"},
		}, nil
	})

	reg := edgenode.NewRegistry()
	entry := &edgenode.NodeEntry{
		NodeID:        "node-fence-1",
		Alias:         "fence-alias",
		Client:        edgeClient,
		DispatchReady: false, // start pending
	}
	reg.RegisterIfAbsent(entry)
	svc := New(reg, nil)
	ctx := context.Background()

	// 1. Observability lookups still see the pending connection.
	if _, err := svc.ResolveNode("node-fence-1"); err != nil {
		t.Errorf("ResolveNode failed for pending: %v", err)
	}
	snap, err := svc.ResolveNodeSnapshot("node-fence-1")
	if err != nil {
		t.Errorf("ResolveNodeSnapshot failed for pending: %v", err)
	}
	if !snap.Connected {
		t.Errorf("expected Connected=true for pending snapshot, got %v", snap.Connected)
	}

	// 2. Every direct send path fails while pending and puts nothing on the wire.
	if _, err := svc.SubmitRun(ctx, SubmitRunRequest{NodeRef: "node-fence-1", Adapter: "ollama", Target: "model"}); err == nil {
		t.Error("expected SubmitRun on pending node to fail")
	}
	if _, err := svc.submitProviderTunnelDirect(SubmitProviderTunnelRequest{NodeRef: "node-fence-1", Adapter: "openai_compat", Target: "model"}); err == nil {
		t.Error("expected submitProviderTunnelDirect on pending node to fail")
	}
	if _, err := svc.CancelRun(ctx, CancelRunRequest{NodeRef: "node-fence-1", RunID: "run-1"}); err == nil {
		t.Error("expected CancelRun on pending node to fail")
	}
	pendingResp, _ := svc.ExecuteCommand(ctx, &iop.EdgeCommandRequest{
		CommandId:      "cmd-pending",
		Operation:      "provider.command",
		TargetSelector: "node-fence-1",
		Parameters:     map[string]string{"command": "capabilities"},
	}, func(*iop.EdgeCommandEvent) {})
	if pendingResp == nil || pendingResp.Status != "error" {
		t.Errorf("expected ExecuteCommand on pending node to error, got %v", pendingResp.GetStatus())
	}

	if n := len(runReqs) + len(tunnelReqs) + len(cancelReqs); n != 0 {
		t.Fatalf("pending node received %d fire-and-forget wire messages, want 0", n)
	}
	cmdMu.Lock()
	pendingCmds := len(cmdReqs)
	cmdMu.Unlock()
	if pendingCmds != 0 {
		t.Fatalf("pending node received %d command wire types, want 0", pendingCmds)
	}

	// 3. Ready opens dispatch.
	if _, transitioned, ok := reg.MarkDispatchReadyIfClient("node-fence-1", edgeClient); !ok || !transitioned {
		t.Fatalf("failed to transition node to ready: ok=%v transitioned=%v", ok, transitioned)
	}
	snap2, err := svc.ResolveNodeSnapshot("node-fence-1")
	if err != nil {
		t.Errorf("ResolveNodeSnapshot failed after ready: %v", err)
	}
	if !snap2.Connected {
		t.Errorf("expected Connected=true after ready, got %v", snap2.Connected)
	}

	// 4. Each direct wire now dispatches exactly once with its exact payload.
	if _, err := svc.SubmitRun(ctx, SubmitRunRequest{
		NodeRef: "node-fence-1", RunID: "run-fence-run", Adapter: "ollama", Target: "model",
		SessionID: "sess-run", Background: true, Prompt: "hello-fence",
		Metadata: map[string]string{"source": "fence-test"},
	}); err != nil {
		t.Errorf("SubmitRun after ready: %v", err)
	}
	if _, err := svc.submitProviderTunnelDirect(SubmitProviderTunnelRequest{
		NodeRef: "node-fence-1", RunID: "run-fence-tunnel", Adapter: "openai_compat", Target: "model",
		SessionID: "sess-tunnel", Method: "POST", Path: "/v1/chat", Body: []byte("tunnel-body"),
	}); err != nil {
		t.Errorf("submitProviderTunnelDirect after ready: %v", err)
	}
	if _, err := svc.CancelRun(ctx, CancelRunRequest{
		NodeRef: "node-fence-1", RunID: "run-fence-cancel",
	}); err != nil {
		t.Errorf("CancelRun after ready: %v", err)
	}
	if _, err := svc.TransportStatus(ctx, NodeCommandRequestSpec{
		NodeRef: "node-fence-1", Adapter: "ollama", Target: "model", SessionID: "sess-transport",
	}); err != nil {
		t.Errorf("TransportStatus after ready: %v", err)
	}
	if _, err := svc.OllamaAPI(ctx, OllamaAPIRequest{
		NodeRef: "node-fence-1", Adapter: "ollama", Target: "llama", Method: "POST", Path: "/api/chat", Body: "ollama-body",
	}); err != nil {
		t.Errorf("OllamaAPI after ready: %v", err)
	}
	readyResp, _ := svc.ExecuteCommand(ctx, &iop.EdgeCommandRequest{
		CommandId:      "cmd-ready",
		Operation:      "provider.command",
		TargetSelector: "node-fence-1",
		Parameters:     map[string]string{"command": "capabilities"},
	}, func(*iop.EdgeCommandEvent) {})
	if readyResp == nil || readyResp.Status == "error" {
		t.Errorf("ExecuteCommand after ready failed: %v", readyResp.GetError())
	}

	// 4.1 RunRequest identity.
	run := recvWire(t, runReqs, "RunRequest")
	if got := run.GetRunId(); got != "run-fence-run" {
		t.Errorf("RunRequest.run_id = %q, want run-fence-run", got)
	}
	if got := run.GetAdapter(); got != "ollama" {
		t.Errorf("RunRequest.adapter = %q, want ollama", got)
	}
	if got := run.GetTarget(); got != "model" {
		t.Errorf("RunRequest.target = %q, want model", got)
	}
	if !run.GetBackground() {
		t.Errorf("RunRequest.background = false, want true")
	}
	if got := run.GetSessionId(); got != "sess-run" {
		t.Errorf("RunRequest.session_id = %q, want sess-run", got)
	}
	if got := run.GetInput().GetFields()["prompt"].GetStringValue(); got != "hello-fence" {
		t.Errorf("RunRequest.input.prompt = %q, want hello-fence", got)
	}
	if got := run.GetMetadata()["source"]; got != "fence-test" {
		t.Errorf("RunRequest.metadata.source = %q, want fence-test", got)
	}
	assertNoExtra(t, runReqs, "RunRequest")

	// 4.2 ProviderTunnelRequest identity.
	tunnel := recvWire(t, tunnelReqs, "ProviderTunnelRequest")
	if got := tunnel.GetRunId(); got != "run-fence-tunnel" {
		t.Errorf("ProviderTunnelRequest.run_id = %q, want run-fence-tunnel", got)
	}
	if got := tunnel.GetTunnelId(); got != "run-fence-tunnel-tunnel" {
		t.Errorf("ProviderTunnelRequest.tunnel_id = %q, want run-fence-tunnel-tunnel", got)
	}
	if got := tunnel.GetAdapter(); got != "openai_compat" {
		t.Errorf("ProviderTunnelRequest.adapter = %q, want openai_compat", got)
	}
	if got := tunnel.GetTarget(); got != "model" {
		t.Errorf("ProviderTunnelRequest.target = %q, want model", got)
	}
	if got := tunnel.GetMethod(); got != "POST" {
		t.Errorf("ProviderTunnelRequest.method = %q, want POST", got)
	}
	if got := tunnel.GetPath(); got != "/v1/chat" {
		t.Errorf("ProviderTunnelRequest.path = %q, want /v1/chat", got)
	}
	if got := string(tunnel.GetBody()); got != "tunnel-body" {
		t.Errorf("ProviderTunnelRequest.body = %q, want tunnel-body", got)
	}
	if got := tunnel.GetSessionId(); got != "sess-tunnel" {
		t.Errorf("ProviderTunnelRequest.session_id = %q, want sess-tunnel", got)
	}
	assertNoExtra(t, tunnelReqs, "ProviderTunnelRequest")

	// 4.3 CancelRequest is scoped to one run id.
	cancelRun := recvWire(t, cancelReqs, "CancelRequest")
	if got := cancelRun.GetRunId(); got != "run-fence-cancel" {
		t.Errorf("CancelRequest.run_id = %q, want run-fence-cancel", got)
	}
	assertNoExtra(t, cancelReqs, "CancelRequest")

	// 4.4 NodeCommandRequests: each type exactly once with its exact fields.
	cmdMu.Lock()
	defer cmdMu.Unlock()
	expectCommand := func(typ iop.NodeCommandType, wantAdapter, wantTarget, wantSession, wantIDPrefix string, extra func(*iop.NodeCommandRequest)) {
		reqs := cmdReqs[typ]
		if len(reqs) != 1 {
			t.Errorf("command %v received %d times, want 1", typ, len(reqs))
			return
		}
		req := reqs[0]
		if got := req.GetAdapter(); got != wantAdapter {
			t.Errorf("%v adapter = %q, want %q", typ, got, wantAdapter)
		}
		if got := req.GetTarget(); got != wantTarget {
			t.Errorf("%v target = %q, want %q", typ, got, wantTarget)
		}
		if got := req.GetSessionId(); got != wantSession {
			t.Errorf("%v session_id = %q, want %q", typ, got, wantSession)
		}
		if got := req.GetRequestId(); !strings.HasPrefix(got, wantIDPrefix) {
			t.Errorf("%v request_id = %q, want prefix %q", typ, got, wantIDPrefix)
		}
		if extra != nil {
			extra(req)
		}
	}
	expectCommand(iop.NodeCommandType_NODE_COMMAND_TYPE_TRANSPORT_STATUS, "ollama", "model", "sess-transport", "transport-", nil)
	expectCommand(iop.NodeCommandType_NODE_COMMAND_TYPE_OLLAMA_API, "ollama", "llama", "default", "ollama-", func(req *iop.NodeCommandRequest) {
		if got := req.GetMetadata()["ollama_method"]; got != "POST" {
			t.Errorf("OLLAMA_API metadata.ollama_method = %q, want POST", got)
		}
		if got := req.GetMetadata()["ollama_path"]; got != "/api/chat" {
			t.Errorf("OLLAMA_API metadata.ollama_path = %q, want /api/chat", got)
		}
		if got := req.GetMetadata()["ollama_body"]; got != "ollama-body" {
			t.Errorf("OLLAMA_API metadata.ollama_body = %q, want ollama-body", got)
		}
	})
	expectCommand(iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES, "", "", "default", "caps-", nil)
	if len(cmdReqs) != 3 {
		t.Errorf("received %d distinct command types, want 3", len(cmdReqs))
	}
}

// recvWire returns the next captured wire of type T or fails after a timeout.
func recvWire[T proto.Message](t *testing.T, ch chan T, name string) T {
	t.Helper()
	select {
	case v := <-ch:
		return v
	case <-time.After(2 * time.Second):
		t.Fatalf("timed out waiting for %s wire", name)
		var zero T
		return zero
	}
}

// assertNoExtra fails if any additional wire of type T is buffered. No further
// send was issued after the expected ones, so a non-blocking read is a
// deterministic exactly-once guard.
func assertNoExtra[T proto.Message](t *testing.T, ch chan T, name string) {
	t.Helper()
	select {
	case v := <-ch:
		t.Errorf("unexpected extra %s wire: %v", name, v)
	default:
	}
}

// gateConn blocks the first (and every subsequent) Write until release is
// closed, signalling entered once the write is in progress. The provider-pool
// dispatch performs its wire send inside WithCurrentDispatchOwner while holding
// the registry lock, so blocking the write here holds that lock and lets a test
// schedule a competing disconnect deterministically against it.
type gateConn struct {
	net.Conn
	entered   chan struct{}
	release   chan struct{}
	enterOnce sync.Once
}

func (c *gateConn) Write(b []byte) (int, error) {
	c.enterOnce.Do(func() { close(c.entered) })
	<-c.release
	return c.Conn.Write(b)
}

// newProviderPoolRaceService builds a Service whose registry holds one ready
// node with a single capacity-1 provider serving model-1. provType selects the
// dispatch path: an OpenAI-compatible type (e.g. "vllm") routes to the tunnel
// path, everything else (e.g. "openai") to the normalized run path.
func newProviderPoolRaceService(t *testing.T, nodeID, provType string, edgeClient *toki.TcpClient) (*Service, *edgenode.Registry, *edgenode.NodeEntry) {
	t.Helper()
	reg := edgenode.NewRegistry()
	entry := &edgenode.NodeEntry{
		NodeID:        nodeID,
		Alias:         nodeID + "-alias",
		Client:        edgeClient,
		DispatchReady: true,
	}
	reg.Register(entry)
	svc := New(reg, edgeevents.NewBus())
	store, err := edgenode.LoadFromConfig([]config.NodeDefinition{
		{ID: nodeID, Alias: nodeID + "-alias", Token: "token", Providers: []config.NodeProviderConf{
			{ID: "prov-1", Type: provType, Models: []string{"model-1"}, Health: "available", Capacity: 1},
		}},
	})
	if err != nil {
		t.Fatalf("load node store: %v", err)
	}
	svc.SetNodeStore(store)
	svc.SetModelCatalog([]config.ModelCatalogEntry{{ID: "model-1", Providers: map[string]string{"prov-1": "model-1"}}})
	return svc, reg, entry
}

// assertQueueSettled asserts every lease is released and every provider/group
// in-flight counter is back to zero, so a race can never leak a slot.
func assertQueueSettled(t *testing.T, q *modelQueueManager) {
	t.Helper()
	q.mu.Lock()
	defer q.mu.Unlock()
	if len(q.leases) != 0 {
		t.Errorf("leases not settled: %d remain", len(q.leases))
	}
	if len(q.leaseByRun) != 0 {
		t.Errorf("leaseByRun not settled: %d remain", len(q.leaseByRun))
	}
	for key, res := range q.resources {
		if res.inFlight != 0 || res.longInFlight != 0 {
			t.Errorf("provider resource %v not settled: inFlight=%d longInFlight=%d", key, res.inFlight, res.longInFlight)
		}
	}
	for gk, g := range q.groups {
		for slot, n := range g.inflight {
			if n != 0 {
				t.Errorf("group %q slot %q inflight=%d, want 0", gk, slot, n)
			}
		}
		for slot, n := range g.longInflight {
			if n != 0 {
				t.Errorf("group %q slot %q longInflight=%d, want 0", gk, slot, n)
			}
		}
	}
}

// assertTrackedLease proves the send-winning barrier was reached only after the
// exact run identity had been bound to one live lease for the expected owner
// generation. The later disconnect/terminal assertions then prove that same
// identity is removed without leaving a duplicate decrement behind.
func assertTrackedLease(t *testing.T, q *modelQueueManager, runID, nodeID string, generation uint64) {
	t.Helper()
	q.mu.Lock()
	defer q.mu.Unlock()
	leaseID, ok := q.leaseByRun[runID]
	if !ok || leaseID == 0 {
		t.Fatalf("run %q has no tracked lease", runID)
	}
	lease := q.leases[leaseID]
	if lease == nil {
		t.Fatalf("run %q maps to missing lease %d", runID, leaseID)
	}
	if lease.nodeID != nodeID || lease.generation != generation {
		t.Fatalf("run %q lease owner=(node=%q generation=%d), want (%q,%d)",
			runID, lease.nodeID, lease.generation, nodeID, generation)
	}
}

func disconnectCurrentOwner(svc *Service, reg *edgenode.Registry, entry *edgenode.NodeEntry, reason string) (uint64, bool) {
	generation, ok := reg.UnregisterIfClient(entry.NodeID, entry.Client)
	if ok {
		svc.HandleNodeDisconnect(entry.NodeID, generation, reason)
	}
	return generation, ok
}

// TestProviderPoolDispatchRunDisconnectRace pins the normalized send/disconnect
// race in two deterministic directions instead of inferring order from sleeps.
//
//   - send-winning: the dispatch enters its wire send under the owner gate
//     (registry lock held); a disconnect racing in blocks on that lock, the wire
//     is written exactly once, and the authoritative disconnect then releases the
//     handed-off lease exactly once, leaving every counter at zero.
//   - disconnect-winning: the disconnect settles (unregister + fence) while the
//     dispatch is parked at its post-admission PrepareRun barrier, before the
//     owner gate. The gate then fences the stale generation, so no wire and no
//     run handle escape and the reserved lease is released exactly once.
func TestProviderPoolDispatchRunDisconnectRace(t *testing.T) {
	runParser := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	t.Run("send-winning", func(t *testing.T) {
		edgeConn, nodeConn := net.Pipe()
		defer edgeConn.Close()
		defer nodeConn.Close()
		gate := &gateConn{Conn: edgeConn, entered: make(chan struct{}), release: make(chan struct{})}
		edgeClient := toki.NewTcpClient(gate, 0, 0, runParser)
		nodeClient := toki.NewTcpClient(nodeConn, 0, 0, runParser)
		runReqs := make(chan *iop.RunRequest, 4)
		toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(req *iop.RunRequest) {
			runReqs <- proto.Clone(req).(*iop.RunRequest)
		})

		svc, reg, entry := newProviderPoolRaceService(t, "node-race-run", "openai", edgeClient)

		dispatchDone := make(chan struct{})
		var result *ProviderPoolDispatchResult
		var dispatchErr error
		go func() {
			result, dispatchErr = svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
				Run: SubmitRunRequest{NodeRef: "node-race-run", RunID: "run-send-win", ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true, Background: true},
			})
			close(dispatchDone)
		}()

		select {
		case <-gate.entered:
		case <-time.After(3 * time.Second):
			t.Fatal("dispatch did not reach the wire send")
		}
		assertTrackedLease(t, svc.queue, "run-send-win", "node-race-run", entry.ConnectionGeneration)

		type disconnectOutcome struct {
			generation uint64
			ok         bool
		}
		disconnectDone := make(chan disconnectOutcome, 1)
		go func() {
			generation, ok := disconnectCurrentOwner(svc, reg, entry, "test-disconnect")
			disconnectDone <- disconnectOutcome{generation: generation, ok: ok}
		}()

		close(gate.release)
		<-dispatchDone
		disconnected := <-disconnectDone
		if !disconnected.ok || disconnected.generation != entry.ConnectionGeneration {
			t.Fatalf("send-winning disconnect=(generation=%d ok=%v), want (%d,true)",
				disconnected.generation, disconnected.ok, entry.ConnectionGeneration)
		}

		if dispatchErr != nil {
			t.Fatalf("send-winning dispatch must succeed, got %v", dispatchErr)
		}
		if result == nil || result.Run == nil {
			t.Fatal("send-winning must return a run handle")
		}
		wire := recvWire(t, runReqs, "RunRequest")
		if got := wire.GetRunId(); got != "run-send-win" {
			t.Errorf("wire run_id = %q, want run-send-win", got)
		}
		assertNoExtra(t, runReqs, "RunRequest")
		assertQueueSettled(t, svc.queue)

		// A terminal callback losing to the authoritative disconnect is an
		// idempotent no-op for the same tracked lease; closing the handle must not
		// reopen or double-release it either.
		svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: wire.GetRunId(), Type: "complete", NodeId: "node-race-run"})
		result.Run.Close()
		assertQueueSettled(t, svc.queue)
	})

	t.Run("disconnect-winning", func(t *testing.T) {
		edgeConn, nodeConn := net.Pipe()
		defer edgeConn.Close()
		defer nodeConn.Close()
		edgeClient := toki.NewTcpClient(edgeConn, 0, 0, runParser)
		nodeClient := toki.NewTcpClient(nodeConn, 0, 0, runParser)
		runReqs := make(chan *iop.RunRequest, 4)
		toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(req *iop.RunRequest) {
			runReqs <- proto.Clone(req).(*iop.RunRequest)
		})

		svc, reg, entry := newProviderPoolRaceService(t, "node-race-run", "openai", edgeClient)

		reachedPrepare := make(chan struct{})
		releasePrepare := make(chan struct{})
		dispatchDone := make(chan struct{})
		var result *ProviderPoolDispatchResult
		var dispatchErr error
		go func() {
			result, dispatchErr = svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
				Run: SubmitRunRequest{NodeRef: "node-race-run", RunID: "run-disc-win", ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true, Background: true},
				PrepareRun: func(req SubmitRunRequest) (SubmitRunRequest, error) {
					close(reachedPrepare)
					<-releasePrepare
					return req, nil
				},
			})
			close(dispatchDone)
		}()

		<-reachedPrepare
		if got := leaseCount(svc.queue); got != 1 {
			t.Fatalf("disconnect-winning reserved leases=%d before fence, want 1", got)
		}
		disconnectedGen, ok := disconnectCurrentOwner(svc, reg, entry, "test-disconnect")
		if !ok || disconnectedGen != entry.ConnectionGeneration {
			t.Fatalf("disconnect-winning disconnect=(generation=%d ok=%v), want (%d,true)",
				disconnectedGen, ok, entry.ConnectionGeneration)
		}
		close(releasePrepare)
		<-dispatchDone

		if dispatchErr == nil {
			t.Fatal("disconnect-winning dispatch must fail")
		}
		if result != nil {
			t.Fatalf("disconnect-winning must not return a result, got %+v", result)
		}
		assertNoExtra(t, runReqs, "RunRequest")
		assertQueueSettled(t, svc.queue)
	})
}

// TestProviderPoolDispatchTunnelDisconnectRace mirrors the normalized race for
// the tunnel/passthrough path: a send-winning subtest that writes exactly one
// ProviderTunnelRequest then releases the lease on the authoritative disconnect,
// and a disconnect-winning subtest whose PrepareTunnel barrier lets the fence
// win so no tunnel frame and no handle escape.
func TestProviderPoolDispatchTunnelDisconnectRace(t *testing.T) {
	tunnelParser := toki.ParserMap{
		toki.TypeNameOf(&iop.ProviderTunnelRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.ProviderTunnelRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	t.Run("send-winning", func(t *testing.T) {
		edgeConn, nodeConn := net.Pipe()
		defer edgeConn.Close()
		defer nodeConn.Close()
		gate := &gateConn{Conn: edgeConn, entered: make(chan struct{}), release: make(chan struct{})}
		edgeClient := toki.NewTcpClient(gate, 0, 0, tunnelParser)
		nodeClient := toki.NewTcpClient(nodeConn, 0, 0, tunnelParser)
		tunnelReqs := make(chan *iop.ProviderTunnelRequest, 4)
		toki.AddListenerTyped[*iop.ProviderTunnelRequest](&nodeClient.Communicator, func(req *iop.ProviderTunnelRequest) {
			tunnelReqs <- proto.Clone(req).(*iop.ProviderTunnelRequest)
		})

		svc, reg, entry := newProviderPoolRaceService(t, "node-race-tunnel", "vllm", edgeClient)

		dispatchDone := make(chan struct{})
		var result *ProviderPoolDispatchResult
		var dispatchErr error
		go func() {
			result, dispatchErr = svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
				Run:    SubmitRunRequest{NodeRef: "node-race-tunnel", ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true},
				Tunnel: SubmitProviderTunnelRequest{NodeRef: "node-race-tunnel", RunID: "run-tunnel-send-win", ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true},
			})
			close(dispatchDone)
		}()

		select {
		case <-gate.entered:
		case <-time.After(3 * time.Second):
			t.Fatal("tunnel dispatch did not reach the wire send")
		}
		assertTrackedLease(t, svc.queue, "run-tunnel-send-win", "node-race-tunnel", entry.ConnectionGeneration)

		type disconnectOutcome struct {
			generation uint64
			ok         bool
		}
		disconnectDone := make(chan disconnectOutcome, 1)
		go func() {
			generation, ok := disconnectCurrentOwner(svc, reg, entry, "test-disconnect")
			disconnectDone <- disconnectOutcome{generation: generation, ok: ok}
		}()

		close(gate.release)
		<-dispatchDone
		disconnected := <-disconnectDone
		if !disconnected.ok || disconnected.generation != entry.ConnectionGeneration {
			t.Fatalf("send-winning tunnel disconnect=(generation=%d ok=%v), want (%d,true)",
				disconnected.generation, disconnected.ok, entry.ConnectionGeneration)
		}

		if dispatchErr != nil {
			t.Fatalf("send-winning tunnel dispatch must succeed, got %v", dispatchErr)
		}
		if result == nil || result.Tunnel == nil {
			t.Fatal("send-winning must return a tunnel handle")
		}
		wire := recvWire(t, tunnelReqs, "ProviderTunnelRequest")
		if got := wire.GetRunId(); got != "run-tunnel-send-win" {
			t.Errorf("wire run_id = %q, want run-tunnel-send-win", got)
		}
		assertNoExtra(t, tunnelReqs, "ProviderTunnelRequest")
		assertQueueSettled(t, svc.queue)

		// Deliver the competing tunnel terminal after disconnect. The handle sees
		// exactly one END and closes, while the already-settled lease/counters stay
		// at zero when Close races afterward.
		stream := result.Tunnel.Stream()
		svc.RouteProviderTunnelFrame(&iop.ProviderTunnelFrame{
			RunId: wire.GetRunId(), TunnelId: wire.GetTunnelId(),
			Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true,
		})
		select {
		case terminal, ok := <-stream.Frames:
			if !ok || terminal.GetKind() != iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END || !terminal.GetEnd() {
				t.Fatalf("tunnel terminal=%v open=%v, want one END frame", terminal, ok)
			}
		case <-time.After(2 * time.Second):
			t.Fatal("timed out waiting for tunnel END frame")
		}
		select {
		case extra, ok := <-stream.Frames:
			if ok {
				t.Fatalf("unexpected extra tunnel terminal/frame: %v", extra)
			}
		case <-time.After(2 * time.Second):
			t.Fatal("tunnel stream did not close after END")
		}
		result.Tunnel.Close()
		assertQueueSettled(t, svc.queue)
	})

	t.Run("disconnect-winning", func(t *testing.T) {
		edgeConn, nodeConn := net.Pipe()
		defer edgeConn.Close()
		defer nodeConn.Close()
		edgeClient := toki.NewTcpClient(edgeConn, 0, 0, tunnelParser)
		nodeClient := toki.NewTcpClient(nodeConn, 0, 0, tunnelParser)
		tunnelReqs := make(chan *iop.ProviderTunnelRequest, 4)
		toki.AddListenerTyped[*iop.ProviderTunnelRequest](&nodeClient.Communicator, func(req *iop.ProviderTunnelRequest) {
			tunnelReqs <- proto.Clone(req).(*iop.ProviderTunnelRequest)
		})

		svc, reg, entry := newProviderPoolRaceService(t, "node-race-tunnel", "vllm", edgeClient)

		reachedPrepare := make(chan struct{})
		releasePrepare := make(chan struct{})
		dispatchDone := make(chan struct{})
		var result *ProviderPoolDispatchResult
		var dispatchErr error
		go func() {
			result, dispatchErr = svc.SubmitProviderPool(context.Background(), ProviderPoolDispatchRequest{
				Run:    SubmitRunRequest{NodeRef: "node-race-tunnel", ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true},
				Tunnel: SubmitProviderTunnelRequest{NodeRef: "node-race-tunnel", RunID: "run-tunnel-disc-win", ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true},
				PrepareTunnel: func(req SubmitProviderTunnelRequest) (SubmitProviderTunnelRequest, error) {
					close(reachedPrepare)
					<-releasePrepare
					return req, nil
				},
			})
			close(dispatchDone)
		}()

		<-reachedPrepare
		if got := leaseCount(svc.queue); got != 1 {
			t.Fatalf("disconnect-winning tunnel reserved leases=%d before fence, want 1", got)
		}
		disconnectedGen, ok := disconnectCurrentOwner(svc, reg, entry, "test-disconnect")
		if !ok || disconnectedGen != entry.ConnectionGeneration {
			t.Fatalf("disconnect-winning tunnel disconnect=(generation=%d ok=%v), want (%d,true)",
				disconnectedGen, ok, entry.ConnectionGeneration)
		}
		close(releasePrepare)
		<-dispatchDone

		if dispatchErr == nil {
			t.Fatal("disconnect-winning tunnel dispatch must fail")
		}
		if result != nil {
			t.Fatalf("disconnect-winning must not return a result, got %+v", result)
		}
		assertNoExtra(t, tunnelReqs, "ProviderTunnelRequest")
		assertQueueSettled(t, svc.queue)
	})
}

// providerResourceState reads a provider resource's in-flight count and orphan
// flag under the manager lock for white-box assertions.
func providerResourceInflight(q *modelQueueManager, nodeID, providerID string) (inFlight int, orphan, ok bool) {
	q.mu.Lock()
	defer q.mu.Unlock()
	res, exists := q.resources[providerResourceKey{nodeID: nodeID, providerID: providerID}]
	if !exists {
		return 0, false, false
	}
	return res.inFlight, res.orphan, true
}

func queuedItemCount(q *modelQueueManager) int {
	q.mu.Lock()
	defer q.mu.Unlock()
	n := 0
	for _, g := range q.groups {
		n += len(g.queue)
	}
	return n
}

// TestReconnectActivationLinearizesAgainstDisconnect drives the real queue
// through an actual reconnect and proves the ownership linearization the fix
// establishes: a stranded provider-pool waiter is pumped exactly once, and only
// by the current reconnect generation. A stale (superseded) generation's
// HandleNodeConnect is a no-op — it neither re-activates the orphaned resource
// nor pumps the waiter — while the current generation activates and dispatches it
// exactly once, and all counters settle to zero afterward.
func TestReconnectActivationLinearizesAgainstDisconnect(t *testing.T) {
	parser := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	altEdge, altNode := net.Pipe()
	defer altEdge.Close()
	defer altNode.Close()
	reconEdge, reconNode := net.Pipe()
	defer reconEdge.Close()
	defer reconNode.Close()

	altEdgeClient := toki.NewTcpClient(altEdge, 0, 0, parser)
	altNodeClient := toki.NewTcpClient(altNode, 0, 0, parser)
	reconEdgeClient := toki.NewTcpClient(reconEdge, 0, 0, parser)
	reconNodeClient := toki.NewTcpClient(reconNode, 0, 0, parser)

	altRuns := make(chan *iop.RunRequest, 4)
	reconRuns := make(chan *iop.RunRequest, 4)
	toki.AddListenerTyped[*iop.RunRequest](&altNodeClient.Communicator, func(req *iop.RunRequest) {
		altRuns <- proto.Clone(req).(*iop.RunRequest)
	})
	toki.AddListenerTyped[*iop.RunRequest](&reconNodeClient.Communicator, func(req *iop.RunRequest) {
		reconRuns <- proto.Clone(req).(*iop.RunRequest)
	})

	reg := edgenode.NewRegistry()
	altEntry := &edgenode.NodeEntry{NodeID: "node-alt", Alias: "alt", Client: altEdgeClient, DispatchReady: true}
	reg.Register(altEntry)
	reconEntry := &edgenode.NodeEntry{NodeID: "node-recon", Alias: "recon", Client: reconEdgeClient, DispatchReady: true}
	reg.Register(reconEntry)
	gen1 := reconEntry.ConnectionGeneration

	svc := New(reg, edgeevents.NewBus())
	store, err := edgenode.LoadFromConfig([]config.NodeDefinition{
		{ID: "node-alt", Alias: "alt", Token: "alt-token", Providers: []config.NodeProviderConf{
			{ID: "prov-alt", Type: "openai", Models: []string{"model-1"}, Health: "available", Capacity: 1},
		}},
		{ID: "node-recon", Alias: "recon", Token: "recon-token", Providers: []config.NodeProviderConf{
			{ID: "prov-recon", Type: "openai", Models: []string{"model-1"}, Health: "available", Capacity: 1},
		}},
	})
	if err != nil {
		t.Fatalf("load node store: %v", err)
	}
	svc.SetNodeStore(store)
	svc.SetModelCatalog([]config.ModelCatalogEntry{{ID: "model-1", Providers: map[string]string{"prov-alt": "model-1", "prov-recon": "model-1"}}})

	svc.HandleNodeConnect("node-alt", altEntry.ConnectionGeneration)
	svc.HandleNodeConnect("node-recon", gen1)

	ctx := context.Background()

	// Occupy prov-alt with a background run. With both providers idle the tie-break
	// (prov-alt < prov-recon) sends it to prov-alt, leaving prov-recon the only
	// candidate a later waiter can reach.
	if _, err := svc.SubmitRun(ctx, SubmitRunRequest{ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true, Background: true}); err != nil {
		t.Fatalf("submit blocking run: %v", err)
	}
	occupy := recvWire(t, altRuns, "RunRequest(alt)")
	if got, _, _ := providerResourceInflight(svc.queue, "node-alt", "prov-alt"); got != 1 {
		t.Fatalf("prov-alt in-flight = %d after blocking run, want 1", got)
	}

	// Disconnect node-recon: its provider resource is orphaned.
	reg.UnregisterIfClient("node-recon", reconEntry.Client)
	svc.HandleNodeDisconnect("node-recon", gen1, "test-disconnect")
	if _, orphan, ok := providerResourceInflight(svc.queue, "node-recon", "prov-recon"); !ok || !orphan {
		t.Fatalf("prov-recon must be orphaned after disconnect (ok=%v orphan=%v)", ok, orphan)
	}

	// Submit the waiter: alt is full and recon is offline, so it strands in queue.
	type outcome struct {
		res RunResult
		err error
	}
	waiterDone := make(chan outcome, 1)
	go func() {
		res, err := svc.SubmitRun(ctx, SubmitRunRequest{ModelGroupKey: "model-1", Adapter: "openai", Target: "model-1", ProviderPool: true, Background: true})
		waiterDone <- outcome{res: res, err: err}
	}()
	waitForCondition(t, func() bool { return queuedItemCount(svc.queue) == 1 }, "waiter did not enqueue")

	// Reconnect node-recon under a strictly higher generation (pending→ready in the
	// real flow; Register here mints the new generation and makes it the owner).
	reconEntry2 := &edgenode.NodeEntry{NodeID: "node-recon", Alias: "recon", Client: reconEdgeClient, DispatchReady: true}
	reg.Register(reconEntry2)
	gen2 := reconEntry2.ConnectionGeneration
	if gen2 <= gen1 {
		t.Fatalf("reconnect generation %d must exceed %d", gen2, gen1)
	}

	// Stale activation: the superseded generation must be a no-op — no resource
	// re-activation, no pump, waiter stays queued.
	svc.HandleNodeConnect("node-recon", gen1)
	if _, orphan, ok := providerResourceInflight(svc.queue, "node-recon", "prov-recon"); !ok || !orphan {
		t.Fatalf("stale activation must leave prov-recon orphaned (ok=%v orphan=%v)", ok, orphan)
	}
	if n := queuedItemCount(svc.queue); n != 1 {
		t.Fatalf("stale activation pumped the waiter: queued=%d, want 1", n)
	}
	select {
	case o := <-waiterDone:
		t.Fatalf("stale activation dispatched the waiter: err=%v", o.err)
	default:
	}
	assertNoExtra(t, reconRuns, "RunRequest(recon)")

	// Current activation: the reconnect generation activates the resource and
	// pumps the stranded waiter to it exactly once.
	svc.HandleNodeConnect("node-recon", gen2)
	var waiter outcome
	select {
	case waiter = <-waiterDone:
	case <-time.After(3 * time.Second):
		t.Fatal("current activation did not dispatch the waiter")
	}
	if waiter.err != nil {
		t.Fatalf("waiter dispatch failed: %v", waiter.err)
	}
	dispatch := waiter.res.Dispatch()
	if dispatch.NodeID != "node-recon" || dispatch.ProviderID != "prov-recon" {
		t.Fatalf("waiter dispatch=(node=%q provider=%q), want node-recon/prov-recon", dispatch.NodeID, dispatch.ProviderID)
	}
	reconWire := recvWire(t, reconRuns, "RunRequest(recon)")
	if reconWire.GetRunId() != dispatch.RunID {
		t.Fatalf("recon wire run_id=%q, want %q", reconWire.GetRunId(), dispatch.RunID)
	}
	assertNoExtra(t, reconRuns, "RunRequest(recon)")
	if got, _, _ := providerResourceInflight(svc.queue, "node-recon", "prov-recon"); got != 1 {
		t.Fatalf("prov-recon in-flight = %d after activation, want 1", got)
	}

	// Settle both runs and confirm no counter leaked.
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: occupy.GetRunId(), Type: "complete"})
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: dispatch.RunID, Type: "complete"})
	waitForCondition(t, func() bool { return inflightRunCount(svc.queue) == 0 }, "runs did not settle")
	assertQueueSettled(t, svc.queue)
}
