package service_test

import (
	"context"
	"errors"
	"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"
	edgeservice "iop/apps/edge/internal/service"
	"iop/packages/go/config"
	iop "iop/proto/gen/iop"
)

func TestSubmitRunReturnsDispatchMetadata(t *testing.T) {
	dispatch := edgeservice.RunDispatch{
		RunID:         "run-x",
		NodeID:        "node-1",
		NodeLabel:     "alpha",
		ModelGroupKey: "codex-model",
		Adapter:       "cli",
		Target:        "codex",
		SessionID:     "session-a",
		Background:    true,
		TimeoutSec:    30,
	}
	handle := &edgeservice.RunHandle{RunDispatch: dispatch}
	if handle.RunID != dispatch.RunID || handle.NodeLabel != dispatch.NodeLabel {
		t.Fatalf("RunHandle does not expose embedded RunDispatch fields: %+v", handle)
	}
	if handle.ModelGroupKey != "codex-model" {
		t.Errorf("RunHandle ModelGroupKey: got %q want codex-model", handle.ModelGroupKey)
	}
	if handle.Background != true || handle.TimeoutSec != 30 {
		t.Errorf("RunHandle dispatch fields wrong: %+v", handle)
	}
}

// TestSubmitRunModelQueueDispatchesQueuedRunAfterDisconnectToLiveNode verifies
// that after a node disconnect, a queued run is dispatched to a remaining live
// node once that node's slot becomes available.
func TestSubmitRunModelQueueDispatchesQueuedRunAfterDisconnectToLiveNode(t *testing.T) {
	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeConn1, nodeConn1 := net.Pipe()
	edgeConn2, nodeConn2 := net.Pipe()
	defer edgeConn1.Close()
	defer nodeConn1.Close()
	defer edgeConn2.Close()
	defer nodeConn2.Close()

	edgeClient1 := toki.NewTcpClient(edgeConn1, 0, 0, parserMap)
	edgeClient2 := toki.NewTcpClient(edgeConn2, 0, 0, parserMap)
	nodeClient1 := toki.NewTcpClient(nodeConn1, 0, 0, parserMap)
	nodeClient2 := toki.NewTcpClient(nodeConn2, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient1.Communicator, func(*iop.RunRequest) {})
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient2.Communicator, func(*iop.RunRequest) {})

	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{NodeID: "dc-node-1", Client: edgeClient1})
	reg.Register(&edgenode.NodeEntry{NodeID: "dc-node-2", Client: edgeClient2})

	catalog := []config.ModelCatalogEntry{
		{
			ID: "dc-group",
			Providers: map[string]string{
				"prov-dc-1": "dc-model",
				"prov-dc-2": "dc-model",
			},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "dc-node-1",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-dc-1", Adapter: "mock", Models: []string{"dc-model"}, Health: "available", Capacity: 1},
		},
	})
	store.Add(&edgenode.NodeRecord{
		ID: "dc-node-2",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-dc-2", Adapter: "mock", Models: []string{"dc-model"}, Health: "available", Capacity: 1},
		},
	})

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

	// Submit two background runs to fill both nodes.
	res1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "dc-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run1: %v", err)
	}
	defer res1.Close()

	res2, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "dc-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run2: %v", err)
	}
	defer res2.Close()

	dispatch1 := res1.Dispatch()
	dispatch2 := res2.Dispatch()
	if dispatch1.NodeID == "" || dispatch2.NodeID == "" || dispatch1.NodeID == dispatch2.NodeID {
		t.Fatalf("expected two different nodes; got %q and %q", dispatch1.NodeID, dispatch2.NodeID)
	}

	// Start run3 in a goroutine — will queue because both nodes are full.
	var (
		res3 edgeservice.RunResult
		err3 error
		wg   sync.WaitGroup
	)
	wg.Add(1)
	go func() {
		defer wg.Done()
		res3, err3 = svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
			ModelGroupKey: "dc-group",
			ProviderPool:  true,
			Background:    true,
		})
	}()

	// Allow run3 to enter the queue.
	time.Sleep(30 * time.Millisecond)

	// Identify which node is nd1 (to disconnect) and nd2 (to receive run3).
	nd1NodeID := dispatch1.NodeID
	nd2RunID := dispatch2.RunID
	nd2NodeID := dispatch2.NodeID

	// nd1 disconnects — its candidate is removed from run3's queue item. The
	// transport drives these hooks directly; the event bus is observability only.
	// Generation 0 fences the node unconditionally (whole-node release).
	svc.HandleNodeDisconnect(nd1NodeID, 0, "disconnected")

	// nd2's run terminates — now nd2 has capacity, run3 dispatches to nd2.
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: nd2RunID, Type: "complete"})

	wg.Wait()

	if err3 != nil {
		t.Fatalf("run3 error: %v", err3)
	}
	if res3 == nil {
		t.Fatal("run3: expected non-nil result")
	}
	defer res3.Close()

	if got := res3.Dispatch().NodeID; got != nd2NodeID {
		t.Errorf("run3 dispatched to %q, want %q (the live node; nd1=%q disconnected)", got, nd2NodeID, nd1NodeID)
	}
}

// TestSubmitRunModelQueueContextCancelRemovesQueuedItem verifies that cancelling
// the SubmitRun context removes the item from the queue and returns context.Canceled.
func TestSubmitRunModelQueueContextCancelRemovesQueuedItem(t *testing.T) {
	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer nodeConn.Close()

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(*iop.RunRequest) {})

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

	catalog := []config.ModelCatalogEntry{
		{
			ID:        "ctx-group",
			Providers: map[string]string{"prov-ctx-1": "ctx-model"},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "ctx-node-1",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-ctx-1", Adapter: "mock", Models: []string{"ctx-model"}, Health: "available", Capacity: 1},
		},
	})

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

	// Fill the node capacity with run1.
	res1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "ctx-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run1: %v", err)
	}
	defer res1.Close()

	// Start run2 with a cancellable context (will queue).
	ctx, cancel := context.WithCancel(context.Background())
	var run2Err error
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		_, run2Err = svc.SubmitRun(ctx, edgeservice.SubmitRunRequest{
			ModelGroupKey: "ctx-group",
			ProviderPool:  true,
			Background:    true,
		})
	}()

	// Give run2 time to enter the queue.
	time.Sleep(30 * time.Millisecond)
	cancel()

	wg.Wait()

	if run2Err == nil {
		t.Fatal("expected error from cancelled context, got nil")
	}
	if !errors.Is(run2Err, context.Canceled) {
		t.Errorf("expected context.Canceled, got: %v", run2Err)
	}
}

// TestSubmitRunModelQueueDispatchesAcrossNodes verifies that concurrent
// SubmitRun calls with the same ModelGroupKey are dispatched across multiple
// nodes when each node has capacity 1.
func TestSubmitRunModelQueueDispatchesAcrossNodes(t *testing.T) {
	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.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)
		},
	}

	edgeConn1, nodeConn1 := net.Pipe()
	edgeConn2, nodeConn2 := net.Pipe()
	defer edgeConn1.Close()
	defer nodeConn1.Close()
	defer edgeConn2.Close()
	defer nodeConn2.Close()

	edgeClient1 := toki.NewTcpClient(edgeConn1, 0, 0, parserMap)
	edgeClient2 := toki.NewTcpClient(edgeConn2, 0, 0, parserMap)

	// Node-side clients consume RunRequest messages without responding.
	nodeClient1 := toki.NewTcpClient(nodeConn1, 0, 0, parserMap)
	nodeClient2 := toki.NewTcpClient(nodeConn2, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient1.Communicator, func(*iop.RunRequest) {})
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient2.Communicator, func(*iop.RunRequest) {})

	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{NodeID: "mq-node-1", Client: edgeClient1})
	reg.Register(&edgenode.NodeEntry{NodeID: "mq-node-2", Client: edgeClient2})

	catalog := []config.ModelCatalogEntry{
		{
			ID: "test-group",
			Providers: map[string]string{
				"prov-mq-1": "served-model",
				"prov-mq-2": "served-model",
			},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID:    "mq-node-1",
		Token: "tok-1",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-mq-1", Adapter: "mock", Models: []string{"served-model"}, Health: "available", Capacity: 1},
		},
	})
	store.Add(&edgenode.NodeRecord{
		ID:    "mq-node-2",
		Token: "tok-2",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-mq-2", Adapter: "mock", Models: []string{"served-model"}, Health: "available", Capacity: 1},
		},
	})

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

	var (
		mu      sync.Mutex
		nodeIDs []string
		errs    []error
	)
	var wg sync.WaitGroup
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			res, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
				ModelGroupKey: "test-group",
				ProviderPool:  true,
				Background:    true,
			})
			mu.Lock()
			defer mu.Unlock()
			if err != nil {
				errs = append(errs, err)
				return
			}
			nodeIDs = append(nodeIDs, res.Dispatch().NodeID)
			res.Close()
		}()
	}
	wg.Wait()

	for _, err := range errs {
		t.Fatalf("SubmitRun error: %v", err)
	}

	if len(nodeIDs) != 2 {
		t.Fatalf("expected 2 dispatches, got %d", len(nodeIDs))
	}

	usedNodes := map[string]int{}
	for _, id := range nodeIDs {
		usedNodes[id]++
	}
	if usedNodes["mq-node-1"] != 1 || usedNodes["mq-node-2"] != 1 {
		t.Errorf("expected each node used exactly once: %v", usedNodes)
	}
}

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

	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer nodeConn.Close()

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(*iop.RunRequest) {})

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

	catalog := []config.ModelCatalogEntry{
		{
			ID:        "local-group",
			Providers: map[string]string{"prov-local-1": "local-model"},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "prov-node-1",
		Adapters: config.AdaptersConf{
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama-local", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-local-1", Adapter: "ollama-local", Models: []string{"local-model"}, Health: "available", Capacity: 2, MaxQueue: 3},
		},
	})

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

	res1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "local-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run1 error: %v", err)
	}
	defer res1.Close()

	res2, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "local-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run2 error: %v", err)
	}
	defer res2.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer cancel()
	_, err3 := svc.SubmitRun(ctx, edgeservice.SubmitRunRequest{
		ModelGroupKey: "local-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err3 == nil {
		t.Fatal("expected third run to block and timeout (capacity=2 exceeded)")
	}
	if !errors.Is(err3, context.DeadlineExceeded) {
		t.Errorf("expected deadline exceeded, got: %v", err3)
	}
}

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

	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer nodeConn.Close()

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(*iop.RunRequest) {})

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

	catalog := []config.ModelCatalogEntry{
		{
			ID:        "fallback-group",
			Providers: map[string]string{"prov-fallback-1": "fallback-model"},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "prov-node-fallback",
		Adapters: config.AdaptersConf{
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama-local", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
			},
		},
		Providers: []config.NodeProviderConf{
			// capacity=2, maxQueue=3 come from provider config (provider-pool always uses provider policy).
			{ID: "prov-fallback-1", Adapter: "ollama-local", Models: []string{"fallback-model"}, Health: "available", Capacity: 2, MaxQueue: 3},
		},
	})

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

	res1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "fallback-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run1 error: %v", err)
	}
	defer res1.Close()

	res2, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "fallback-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run2 error: %v", err)
	}
	defer res2.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer cancel()
	_, err3 := svc.SubmitRun(ctx, edgeservice.SubmitRunRequest{
		ModelGroupKey: "fallback-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err3 == nil {
		t.Fatal("expected third run to block and timeout (capacity=2 exceeded)")
	}
	if !errors.Is(err3, context.DeadlineExceeded) {
		t.Errorf("expected deadline exceeded, got: %v", err3)
	}
}

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

	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer nodeConn.Close()

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(*iop.RunRequest) {})

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

	catalog := []config.ModelCatalogEntry{
		{
			ID:        "timeout-group",
			Providers: map[string]string{"prov-timeout-1": "timeout-model"},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "prov-node-timeout",
		Adapters: config.AdaptersConf{
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama-local", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
			},
		},
		Providers: []config.NodeProviderConf{
			// QueueTimeoutMS=10 comes from provider config; provider-pool ignores request-level QueueTimeoutMS.
			{ID: "prov-timeout-1", Adapter: "ollama-local", Models: []string{"timeout-model"}, Health: "available", Capacity: 1, MaxQueue: 5, QueueTimeoutMS: 10},
		},
	})

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

	// Set the canonical provider-pool policy with QueueTimeoutMS=10.
	// The root policy owns the timeout; provider-level QueueTimeoutMS is no
	// longer consulted for provider-pool admission.
	svc.SetRuntimeConfig(store, catalog, edgeservice.NewGroupPolicy(16, 10*time.Millisecond))

	// run 1: fills capacity; root policy sets QueueTimeoutMS=10.
	res1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "timeout-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run1 error: %v", err)
	}
	defer res1.Close()

	// run 2: enters queue; times out after ~10ms per provider policy.
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	start := time.Now()
	_, err2 := svc.SubmitRun(ctx, edgeservice.SubmitRunRequest{
		ModelGroupKey: "timeout-group",
		ProviderPool:  true,
		Background:    true,
	})
	elapsed := time.Since(start)

	if err2 == nil {
		t.Fatal("expected second run to fail with queue timeout")
	}
	if !strings.Contains(err2.Error(), "queue timeout") {
		t.Errorf("expected queue timeout error, got: %v", err2)
	}
	if elapsed > 1*time.Second {
		t.Errorf("expected timeout to happen quickly, took: %v", elapsed)
	}
}

// TestSubmitRunLegacyModelGroupKeyGoesDirectNotQueued verifies that a request
// with ModelGroupKey but ProviderPool=false bypasses the queue and goes directly
// to the single registered node. This proves the queue admission gate on
// req.ProviderPool: without ProviderPool=true, ModelGroupKey is decorative only
// and the service falls back to direct dispatch without touching the model catalog.
func TestSubmitRunLegacyModelGroupKeyGoesDirectNotQueued(t *testing.T) {
	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer nodeConn.Close()

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(*iop.RunRequest) {})

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

	bus := edgeevents.NewBus()
	svc := edgeservice.New(reg, bus)
	// No catalog set — if ProviderPool were true, resolveProviderPoolCandidates would
	// return "not found in catalog". Since ProviderPool=false, direct dispatch succeeds.

	res, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "legacy-group",
		Adapter:       "cli",
		Target:        "codex",
		Background:    true,
		// ProviderPool: false (default)
	})
	if err != nil {
		t.Fatalf("expected direct dispatch to succeed, got: %v", err)
	}
	defer res.Close()

	if res.Dispatch().NodeID != "leg-node-1" {
		t.Errorf("dispatch node: got %q, want leg-node-1", res.Dispatch().NodeID)
	}
}

// TestGlobalPumpNodeExclusionFallsBackAcrossGroups verifies the exclusion +
// cross-group path end to end: a queued request whose preferred node disconnects
// must fall back to the remaining live provider, and the release that frees that
// provider happens in a *different* model group. Before the global pump, the
// waiter's group saw no event of its own and the request sat until timeout.
func TestGlobalPumpNodeExclusionFallsBackAcrossGroups(t *testing.T) {
	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeConn1, nodeConn1 := net.Pipe()
	edgeConn2, nodeConn2 := net.Pipe()
	defer edgeConn1.Close()
	defer nodeConn1.Close()
	defer edgeConn2.Close()
	defer nodeConn2.Close()

	edgeClient1 := toki.NewTcpClient(edgeConn1, 0, 0, parserMap)
	edgeClient2 := toki.NewTcpClient(edgeConn2, 0, 0, parserMap)
	nodeClient1 := toki.NewTcpClient(nodeConn1, 0, 0, parserMap)
	nodeClient2 := toki.NewTcpClient(nodeConn2, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient1.Communicator, func(*iop.RunRequest) {})
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient2.Communicator, func(*iop.RunRequest) {})

	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{NodeID: "xg-node-1", Client: edgeClient1})
	reg.Register(&edgenode.NodeEntry{NodeID: "xg-node-2", Client: edgeClient2})

	// Two model groups share the provider on node 2; only group A can also use
	// the provider on node 1.
	catalog := []config.ModelCatalogEntry{
		{
			ID: "xg-group-a",
			Providers: map[string]string{
				"prov-xg-1": "xg-model-a",
				"prov-xg-2": "xg-model-a",
			},
		},
		{
			ID: "xg-group-b",
			Providers: map[string]string{
				"prov-xg-2": "xg-model-b",
			},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "xg-node-1",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-xg-1", Adapter: "mock", Models: []string{"xg-model-a"}, Health: "available", Capacity: 1},
		},
	})
	store.Add(&edgenode.NodeRecord{
		ID: "xg-node-2",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-xg-2", Adapter: "mock", Models: []string{"xg-model-a", "xg-model-b"}, Health: "available", Capacity: 1},
		},
	})

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

	// Fill node 1 via group A and node 2 via group B.
	resA, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "xg-group-a",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("group A run: %v", err)
	}
	defer resA.Close()
	resB, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "xg-group-b",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("group B run: %v", err)
	}
	defer resB.Close()

	dispatchA := resA.Dispatch()
	dispatchB := resB.Dispatch()
	if dispatchA.NodeID != "xg-node-1" {
		t.Fatalf("group A run landed on %q, want xg-node-1", dispatchA.NodeID)
	}
	if dispatchB.NodeID != "xg-node-2" {
		t.Fatalf("group B run landed on %q, want xg-node-2", dispatchB.NodeID)
	}

	// A second group A request queues: both providers it can use are busy.
	var (
		queued edgeservice.RunResult
		errQ   error
		wg     sync.WaitGroup
	)
	wg.Add(1)
	go func() {
		defer wg.Done()
		queued, errQ = svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
			ModelGroupKey: "xg-group-a",
			ProviderPool:  true,
			Background:    true,
		})
	}()
	time.Sleep(30 * time.Millisecond)

	// Node 1 drops out, removing prov-xg-1 from the waiter's candidates. The only
	// remaining path is prov-xg-2, and the run holding it belongs to group B.
	// Generation 0 fences the node unconditionally (whole-node release).
	svc.HandleNodeDisconnect("xg-node-1", 0, "disconnected")

	// Terminating the group B run must wake the group A waiter.
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: dispatchB.RunID, Type: "complete"})

	wg.Wait()
	if errQ != nil {
		t.Fatalf("queued group A run error: %v", errQ)
	}
	if queued == nil {
		t.Fatal("queued group A run: expected non-nil result")
	}
	defer queued.Close()

	if got := queued.Dispatch().NodeID; got != "xg-node-2" {
		t.Errorf("queued run dispatched to %q, want xg-node-2 (the surviving node freed by another model group)", got)
	}
}

// TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect verifies that
// when a queued run's preferred provider disconnects, the authoritative
// resolve-and-pump pass rebuilds the candidate universe through the live
// resolver, discovers a surviving provider on a different node, and dispatches
// the waiter to that surviving provider — all without waiting for the queue
// timeout. This is the integration path for the REFACTOR-1 change.
func TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect(t *testing.T) {
	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeConn1, nodeConn1 := net.Pipe()
	edgeConn2, nodeConn2 := net.Pipe()
	defer edgeConn1.Close()
	defer nodeConn1.Close()
	defer edgeConn2.Close()
	defer nodeConn2.Close()

	edgeClient1 := toki.NewTcpClient(edgeConn1, 0, 0, parserMap)
	edgeClient2 := toki.NewTcpClient(edgeConn2, 0, 0, parserMap)
	nodeClient1 := toki.NewTcpClient(nodeConn1, 0, 0, parserMap)
	nodeClient2 := toki.NewTcpClient(nodeConn2, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient1.Communicator, func(*iop.RunRequest) {})
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient2.Communicator, func(*iop.RunRequest) {})

	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{NodeID: "fb-node-1", Client: edgeClient1})

	catalog := []config.ModelCatalogEntry{
		{
			ID: "fb-group",
			Providers: map[string]string{
				"prov-fb-1": "fb-model",
				"prov-fb-2": "fb-model",
			},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "fb-node-1",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-fb-1", Adapter: "mock", Models: []string{"fb-model"}, Health: "available", Capacity: 1},
		},
	})
	store.Add(&edgenode.NodeRecord{
		ID: "fb-node-2",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-fb-2", Adapter: "mock", Models: []string{"fb-model"}, Health: "available", Capacity: 1},
		},
	})

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

	// Fill fb-node-1.
	res1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "fb-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run1: %v", err)
	}
	defer res1.Close()
	if dispatch := res1.Dispatch(); dispatch.NodeID != "fb-node-1" || dispatch.ProviderID != "prov-fb-1" {
		t.Fatalf("run1 dispatch = (%q, %q), want (fb-node-1, prov-fb-1)", dispatch.NodeID, dispatch.ProviderID)
	}

	// Start run2 while fb-node-1 is the only live provider. Its capacity is held
	// by run1, so run2 must enter the provider-pool queue.
	type submitOutcome struct {
		result edgeservice.RunResult
		err    error
	}
	run2Ctx, cancelRun2 := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancelRun2()
	run2Done := make(chan submitOutcome, 1)
	go func() {
		result, submitErr := svc.SubmitRun(run2Ctx, edgeservice.SubmitRunRequest{
			ModelGroupKey: "fb-group",
			ProviderPool:  true,
			Background:    true,
		})
		run2Done <- submitOutcome{result: result, err: submitErr}
	}()

	// Synchronize on observable queue state instead of assuming goroutine
	// scheduling with a fixed sleep. The waiter must be pressure on the only live
	// provider and must not have returned before any survivor is connected.
	waitForProviderSnapshotCounts(t, svc, "fb-node-1", "prov-fb-1", 1, 1)
	select {
	case outcome := <-run2Done:
		if outcome.result != nil {
			outcome.result.Close()
		}
		t.Fatalf("run2 completed before survivor registration: %v", outcome.err)
	default:
	}

	// Expose the configured survivor to the live resolver. Registry registration
	// alone has no queue-pump side effect; observing its candidate pressure proves
	// the waiter is still queued with the new live universe before disconnect.
	reg.Register(&edgenode.NodeEntry{NodeID: "fb-node-2", Client: edgeClient2})
	waitForProviderSnapshotCounts(t, svc, "fb-node-2", "prov-fb-2", 0, 1)
	select {
	case outcome := <-run2Done:
		if outcome.result != nil {
			outcome.result.Close()
		}
		t.Fatalf("run2 completed from survivor registration without disconnect: %v", outcome.err)
	default:
	}

	// Mirror the transport's authoritative ordering: current-client ownership is
	// checked and removed first, then that exact generation drives settlement.
	disconnectedGeneration, ok := reg.UnregisterIfClient("fb-node-1", edgeClient1)
	if !ok || disconnectedGeneration == 0 {
		t.Fatalf("authoritative unregister = (%d, %t), want non-zero generation and true", disconnectedGeneration, ok)
	}
	svc.HandleNodeDisconnect("fb-node-1", disconnectedGeneration, "disconnected")

	// The disconnect transition is the only queue wake-up. Completion is bounded
	// and the single submit goroutine must publish exactly one outcome.
	var outcome submitOutcome
	select {
	case outcome = <-run2Done:
	case <-time.After(time.Second):
		t.Fatal("run2 did not complete within 1s of authoritative disconnect")
	}
	select {
	case duplicate := <-run2Done:
		if duplicate.result != nil {
			duplicate.result.Close()
		}
		t.Fatalf("run2 completed more than once: %v", duplicate.err)
	default:
	}
	if outcome.err != nil {
		t.Fatalf("run2 error: %v", outcome.err)
	}
	if outcome.result == nil {
		t.Fatal("run2: expected non-nil result")
	}
	res2 := outcome.result
	defer res2.Close()

	dispatch2 := res2.Dispatch()
	if dispatch2.NodeID != "fb-node-2" || dispatch2.ProviderID != "prov-fb-2" {
		t.Errorf("run2 dispatch = (%q, %q), want (fb-node-2, prov-fb-2)", dispatch2.NodeID, dispatch2.ProviderID)
	}
	if dispatch2.QueueReason != "capacity_full" {
		t.Errorf("run2 queue reason = %q, want capacity_full", dispatch2.QueueReason)
	}

	// The disconnected holder lease and queue item are already gone, while the
	// fallback lease is the sole remaining in-flight reservation.
	provider1 := waitForProviderSnapshotCounts(t, svc, "fb-node-1", "prov-fb-1", 0, 0)
	provider2 := waitForProviderSnapshotCounts(t, svc, "fb-node-2", "prov-fb-2", 1, 0)
	if provider1.GetLongInFlight() != 0 || provider1.GetLongQueued() != 0 {
		t.Errorf("fb-node-1 long counters after disconnect = (%d, %d), want (0, 0)", provider1.GetLongInFlight(), provider1.GetLongQueued())
	}
	if provider2.GetLongInFlight() != 0 || provider2.GetLongQueued() != 0 {
		t.Errorf("fb-node-2 long counters after fallback = (%d, %d), want (0, 0)", provider2.GetLongInFlight(), provider2.GetLongQueued())
	}

	// A terminal event returns the surviving provider lease. Repeating the same
	// event is an idempotence check: all provider and queue counters stay at zero.
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: dispatch2.RunID, Type: "complete"})
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: dispatch2.RunID, Type: "complete"})
	provider1 = waitForProviderSnapshotCounts(t, svc, "fb-node-1", "prov-fb-1", 0, 0)
	provider2 = waitForProviderSnapshotCounts(t, svc, "fb-node-2", "prov-fb-2", 0, 0)
	if provider1.GetLongInFlight() != 0 || provider1.GetLongQueued() != 0 ||
		provider2.GetLongInFlight() != 0 || provider2.GetLongQueued() != 0 {
		t.Fatalf("long counters after cleanup: provider1=(%d, %d), provider2=(%d, %d), want all zero",
			provider1.GetLongInFlight(), provider1.GetLongQueued(), provider2.GetLongInFlight(), provider2.GetLongQueued())
	}
}

func waitForProviderSnapshotCounts(
	t *testing.T,
	svc *edgeservice.Service,
	nodeID string,
	providerID string,
	wantInFlight int32,
	wantQueued int32,
) *iop.ProviderSnapshot {
	t.Helper()

	deadline := time.Now().Add(time.Second)
	var (
		lastInFlight int32
		lastQueued   int32
		found        bool
	)
	for {
		for _, snapshot := range svc.ListNodeSnapshots() {
			if snapshot.NodeID != nodeID {
				continue
			}
			for _, provider := range snapshot.ProviderSnapshots {
				if provider.GetId() != providerID {
					continue
				}
				found = true
				lastInFlight = provider.GetInFlight()
				lastQueued = provider.GetQueued()
				if lastInFlight == wantInFlight && lastQueued == wantQueued {
					return provider
				}
			}
		}
		if !time.Now().Before(deadline) {
			t.Fatalf("provider %s/%s snapshot counts = (%d, %d), want (%d, %d), found=%t",
				nodeID, providerID, lastInFlight, lastQueued, wantInFlight, wantQueued, found)
		}
		time.Sleep(time.Millisecond)
	}
}

// TestSubmitRunModelQueueLastProviderDisconnectReturnsUnavailable verifies that
// when ALL live candidates for a queued run disappear (last provider
// disconnect), the waiter receives a terminal unavailable error immediately
// instead of waiting for the queue timeout. This is the critical
// correctness condition for the REFACTOR-1 authoritative settlement pass.
func TestSubmitRunModelQueueLastProviderDisconnectReturnsUnavailable(t *testing.T) {
	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer nodeConn.Close()

	edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
	nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(*iop.RunRequest) {})

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

	catalog := []config.ModelCatalogEntry{
		{
			ID:        "lp-group",
			Providers: map[string]string{"prov-lp-1": "lp-model"},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "lp-node-1",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-lp-1", Adapter: "mock", Models: []string{"lp-model"}, Health: "available", Capacity: 1},
		},
	})

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

	// Fill the only provider.
	res1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "lp-group",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("run1: %v", err)
	}
	defer res1.Close()

	// Start run2 — will queue because capacity is full.
	var (
		res2 edgeservice.RunResult
		err2 error
		wg   sync.WaitGroup
	)
	wg.Add(1)
	go func() {
		defer wg.Done()
		res2, err2 = svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
			ModelGroupKey: "lp-group",
			ProviderPool:  true,
			Background:    true,
		})
	}()

	time.Sleep(30 * time.Millisecond)

	// Disconnect the last provider — all candidates disappear.
	svc.HandleNodeDisconnect("lp-node-1", 0, "last provider disconnect")

	wg.Wait()

	// The waiter must receive a terminal error (not a successful dispatch).
	if err2 == nil {
		t.Fatal("expected terminal unavailable error for last-provider disconnect, got nil")
	}
	if res2 != nil {
		res2.Close()
		t.Fatalf("expected nil result for last-provider disconnect, got %+v", res2.Dispatch())
	}

	// Verify all accounting is zero via snapshot.
	snaps := svc.ListNodeSnapshots()
	for _, s := range snaps {
		for _, ps := range s.ProviderSnapshots {
			if ps.GetInFlight() != 0 {
				t.Errorf("provider %s on %s: inflight after last disconnect = %d, want 0",
					ps.GetId(), s.NodeID, ps.GetInFlight())
			}
		}
	}
}

// TestReconnectRebuildsDisconnectedCandidateUniverse verifies the S15
// reconnect-candidate-recovery contract through the full SubmitRun path: when a
// waiter's target provider disconnected (dropping it from the live candidate
// universe) and the only alternate is full, the accepted reconnect alone rebuilds
// the candidate universe through the live resolver and dispatches the waiter back
// to the reconnected provider — without any new request, config refresh, or lease
// release. The transport's authoritative ordering (UnregisterIfClient →
// HandleNodeDisconnect on close; RegisterIfAbsent generation → HandleNodeConnect
// on reconnect) is mirrored directly.
func TestReconnectRebuildsDisconnectedCandidateUniverse(t *testing.T) {
	parserMap := toki.ParserMap{
		toki.TypeNameOf(&iop.RunRequest{}): func(b []byte) (proto.Message, error) {
			m := &iop.RunRequest{}
			return m, proto.Unmarshal(b, m)
		},
	}

	// node-alt is the full alternate; node-recon disconnects and reconnects. A
	// second pipe stands in for the reconnect's fresh connection.
	edgeAlt, nodeAlt := net.Pipe()
	edgeRecon1, nodeRecon1 := net.Pipe()
	edgeRecon2, nodeRecon2 := net.Pipe()
	defer edgeAlt.Close()
	defer nodeAlt.Close()
	defer edgeRecon1.Close()
	defer nodeRecon1.Close()
	defer edgeRecon2.Close()
	defer nodeRecon2.Close()

	edgeAltClient := toki.NewTcpClient(edgeAlt, 0, 0, parserMap)
	edgeReconClient1 := toki.NewTcpClient(edgeRecon1, 0, 0, parserMap)
	edgeReconClient2 := toki.NewTcpClient(edgeRecon2, 0, 0, parserMap)
	nodeAltClient := toki.NewTcpClient(nodeAlt, 0, 0, parserMap)
	nodeReconClient1 := toki.NewTcpClient(nodeRecon1, 0, 0, parserMap)
	nodeReconClient2 := toki.NewTcpClient(nodeRecon2, 0, 0, parserMap)
	toki.AddListenerTyped[*iop.RunRequest](&nodeAltClient.Communicator, func(*iop.RunRequest) {})
	toki.AddListenerTyped[*iop.RunRequest](&nodeReconClient1.Communicator, func(*iop.RunRequest) {})
	toki.AddListenerTyped[*iop.RunRequest](&nodeReconClient2.Communicator, func(*iop.RunRequest) {})

	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{NodeID: "node-alt", Client: edgeAltClient})
	reg.Register(&edgenode.NodeEntry{NodeID: "node-recon", Client: edgeReconClient1})

	catalog := []config.ModelCatalogEntry{{
		ID: "rc-group",
		Providers: map[string]string{
			"prov-alt":   "rc-model",
			"prov-recon": "rc-model",
		},
	}}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "node-alt",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"}},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-alt", Adapter: "mock", Models: []string{"rc-model"}, Health: "available", Capacity: 1},
		},
	})
	store.Add(&edgenode.NodeRecord{
		ID: "node-recon",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{{Name: "mock", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"}},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-recon", Adapter: "mock", Models: []string{"rc-model"}, Health: "available", Capacity: 1},
		},
	})

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

	// run1 fills the alternate provider (prov-alt sorts before prov-recon, so the
	// scheduler picks it deterministically for the first admission).
	run1, err := svc.SubmitRun(context.Background(), edgeservice.SubmitRunRequest{
		ModelGroupKey: "rc-group", ProviderPool: true, Background: true,
	})
	if err != nil {
		t.Fatalf("run1: %v", err)
	}
	defer run1.Close()
	if dispatch := run1.Dispatch(); dispatch.NodeID != "node-alt" || dispatch.ProviderID != "prov-alt" {
		t.Fatalf("run1 dispatch = (%q, %q), want (node-alt, prov-alt)", dispatch.NodeID, dispatch.ProviderID)
	}

	// Disconnect node-recon (idle) using the transport's authoritative ordering.
	// This drops prov-recon from the live candidate universe and orphans it.
	disconnectedGeneration, ok := reg.UnregisterIfClient("node-recon", edgeReconClient1)
	if !ok || disconnectedGeneration == 0 {
		t.Fatalf("authoritative unregister = (%d, %t), want non-zero generation and true", disconnectedGeneration, ok)
	}
	svc.HandleNodeDisconnect("node-recon", disconnectedGeneration, "disconnected")

	// run2 now has only prov-alt as a live candidate, and it is full — the request
	// must enter the queue rather than dispatch.
	type submitOutcome struct {
		result edgeservice.RunResult
		err    error
	}
	run2Ctx, cancelRun2 := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancelRun2()
	run2Done := make(chan submitOutcome, 1)
	go func() {
		result, submitErr := svc.SubmitRun(run2Ctx, edgeservice.SubmitRunRequest{
			ModelGroupKey: "rc-group", ProviderPool: true, Background: true,
		})
		run2Done <- submitOutcome{result: result, err: submitErr}
	}()

	// Confirm the waiter is queued against the only live provider before reconnect.
	waitForProviderSnapshotCounts(t, svc, "node-alt", "prov-alt", 1, 1)
	select {
	case outcome := <-run2Done:
		if outcome.result != nil {
			outcome.result.Close()
		}
		t.Fatalf("run2 resolved before reconnect: %v", outcome.err)
	default:
	}

	// Reconnect node-recon on a fresh connection: a strictly higher generation is
	// minted, and the accepted-connect hook alone rebuilds the candidate universe
	// and pumps the waiter. No config refresh, new request, or lease release.
	reconnectEntry := &edgenode.NodeEntry{NodeID: "node-recon", Client: edgeReconClient2}
	reg.Register(reconnectEntry)
	if reconnectEntry.ConnectionGeneration <= disconnectedGeneration {
		t.Fatalf("reconnect generation=%d, want > disconnected generation=%d", reconnectEntry.ConnectionGeneration, disconnectedGeneration)
	}
	svc.HandleNodeConnect("node-recon", reconnectEntry.ConnectionGeneration)

	var outcome submitOutcome
	select {
	case outcome = <-run2Done:
	case <-time.After(time.Second):
		t.Fatal("run2 did not dispatch within 1s of the accepted reconnect")
	}
	if outcome.err != nil {
		t.Fatalf("run2 error: %v", outcome.err)
	}
	if outcome.result == nil {
		t.Fatal("run2: expected non-nil result")
	}
	run2 := outcome.result
	defer run2.Close()

	dispatch2 := run2.Dispatch()
	if dispatch2.NodeID != "node-recon" || dispatch2.ProviderID != "prov-recon" {
		t.Fatalf("run2 dispatch = (%q, %q), want (node-recon, prov-recon)", dispatch2.NodeID, dispatch2.ProviderID)
	}
	if dispatch2.QueueReason != "capacity_full" {
		t.Errorf("run2 queue reason = %q, want capacity_full", dispatch2.QueueReason)
	}

	// The reconnected provider now carries the waiter's lease; the alternate still
	// holds run1 (its lease was never released), proving the reconnect drove the pump.
	waitForProviderSnapshotCounts(t, svc, "node-recon", "prov-recon", 1, 0)
	waitForProviderSnapshotCounts(t, svc, "node-alt", "prov-alt", 1, 0)

	// Terminal events drain both providers; repeating is an idempotence check.
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: run1.Dispatch().RunID, Type: "complete"})
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: dispatch2.RunID, Type: "complete"})
	svc.HandleRunLifecycleEvent(&iop.RunEvent{RunId: dispatch2.RunID, Type: "complete"})
	waitForProviderSnapshotCounts(t, svc, "node-alt", "prov-alt", 0, 0)
	waitForProviderSnapshotCounts(t, svc, "node-recon", "prov-recon", 0, 0)
}
