package node_test

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"iop/apps/node/internal/node"
	"iop/apps/node/internal/runtime"
	"iop/apps/node/internal/transport"
	iop "iop/proto/gen/iop"
)

// --- test doubles used by command tests ---

// commandAdapter supports usage-status and capabilities commands.
type commandAdapter struct {
	lastReq        runtime.CommandRequest
	providerStatus runtime.ProviderStatus
}

func (a *commandAdapter) Name() string { return "command" }
func (a *commandAdapter) Capabilities(_ context.Context) (runtime.Capabilities, error) {
	return runtime.Capabilities{
		AdapterName:    "command",
		Targets:        []string{"v2", "v1"},
		MaxConcurrency: 3,
		ProviderStatus: a.providerStatus,
	}, nil
}
func (a *commandAdapter) Execute(_ context.Context, _ runtime.ExecutionSpec, _ runtime.EventSink) error {
	return nil
}
func (a *commandAdapter) HandleCommand(ctx context.Context, req runtime.CommandRequest) (runtime.CommandResponse, error) {
	a.lastReq = req
	switch req.Type {
	case runtime.CommandTypeUsageStatus:
		return runtime.CommandResponse{
			RequestID: req.RequestID,
			Type:      req.Type,
			Adapter:   req.Adapter,
			Target:    req.Target,
			SessionID: req.SessionID,
			UsageStatus: &runtime.AgentUsageStatus{
				RawOutput: "success",
			},
		}, nil
	default:
		return runtime.CommandResponse{}, errors.New("command not supported by adapter")
	}
}

// proberTestAdapter implements runtime.Prober for provider status probing.
type proberTestAdapter struct {
	providerStatus runtime.ProviderStatus
	probeTargets   []string
	probeErr       error
}

func (a *proberTestAdapter) Name() string { return "prober" }
func (a *proberTestAdapter) Capabilities(_ context.Context) (runtime.Capabilities, error) {
	return runtime.Capabilities{
		AdapterName:    "prober",
		Targets:        []string{"model-a"},
		MaxConcurrency: 3,
		ProviderStatus: a.providerStatus,
	}, nil
}
func (a *proberTestAdapter) Execute(_ context.Context, _ runtime.ExecutionSpec, _ runtime.EventSink) error {
	return nil
}
func (a *proberTestAdapter) ProbeProvider(ctx context.Context, target string) (runtime.ProviderProbeResult, error) {
	if a.probeErr != nil {
		return runtime.ProviderProbeResult{}, a.probeErr
	}
	status := runtime.ProviderStatusAvailable
	found := false
	for _, t := range a.probeTargets {
		if t == target {
			found = true
			break
		}
	}
	if target != "" && !found {
		status = runtime.ProviderStatusUnavailable
	}
	return runtime.ProviderProbeResult{
		AdapterName: "prober",
		Target:      target,
		Targets:     a.probeTargets,
		Status:      status,
	}, nil
}

// instanceKeyAdapter advertises an instance key on capabilities.
type instanceKeyAdapter struct {
	instanceKey string
}

func (a *instanceKeyAdapter) Name() string { return "ollama" }
func (a *instanceKeyAdapter) Capabilities(_ context.Context) (runtime.Capabilities, error) {
	return runtime.Capabilities{
		AdapterName:    "ollama",
		InstanceKey:    a.instanceKey,
		Targets:        []string{"llama3"},
		MaxConcurrency: 4,
	}, nil
}
func (a *instanceKeyAdapter) Execute(_ context.Context, _ runtime.ExecutionSpec, _ runtime.EventSink) error {
	return nil
}

// --- command request tests ---

func TestOnCommandRequest_Success(t *testing.T) {
	ca := &commandAdapter{}
	router := &fixedRouter{adapterName: "command", adapters: make(map[string]runtime.Adapter)}
	router.adapters["command"] = ca
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-1",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_USAGE_STATUS,
		Adapter:   "command",
		Target:    "codex",
		SessionId: "default",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error, got %q", resp.GetError())
	}
	if resp.GetUsageStatus().GetRawOutput() != "success" {
		t.Fatalf("expected success raw output, got %q", resp.GetUsageStatus().GetRawOutput())
	}
	if ca.lastReq.Type != runtime.CommandTypeUsageStatus || ca.lastReq.Target != "codex" {
		t.Fatalf("unexpected last req: %+v", ca.lastReq)
	}
}

func TestOnCommandRequest_MissingAdapter(t *testing.T) {
	router := &fixedRouter{adapterName: "missing", adapters: make(map[string]runtime.Adapter)}
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-1",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_USAGE_STATUS,
		Adapter:   "missing",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if !strings.Contains(resp.GetError(), "not found") {
		t.Fatalf("expected not found error, got %q", resp.GetError())
	}
}

func TestOnCommandRequest_NotSupported(t *testing.T) {
	ta := &terminatingAdapter{}
	router := &fixedRouter{adapterName: "terminating", adapters: make(map[string]runtime.Adapter)}
	router.adapters["terminating"] = ta
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-1",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_USAGE_STATUS,
		Adapter:   "terminating",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if !strings.Contains(resp.GetError(), "does not support commands") {
		t.Fatalf("expected not supported error, got %q", resp.GetError())
	}
}

func TestOnCommandRequest_UnspecifiedRejected(t *testing.T) {
	ca := &commandAdapter{}
	router := &fixedRouter{adapterName: "command", adapters: make(map[string]runtime.Adapter)}
	router.adapters["command"] = ca
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-1",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_UNSPECIFIED,
		Adapter:   "command",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if !strings.Contains(resp.GetError(), "unsupported command type") {
		t.Fatalf("expected unsupported command type error, got %q", resp.GetError())
	}
	// Adapter must not be invoked for unsupported command types.
	if ca.lastReq.RequestID != "" {
		t.Fatalf("expected adapter to be skipped, got lastReq %+v", ca.lastReq)
	}
}

func TestOnCommandRequest_Capabilities(t *testing.T) {
	ca := &commandAdapter{}
	router := &fixedRouter{adapterName: "command", adapters: make(map[string]runtime.Adapter)}
	router.adapters["command"] = ca
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-cap",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
		Adapter:   "command",
		Target:    "codex",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error, got %q", resp.GetError())
	}
	if resp.GetType() != iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES {
		t.Fatalf("type: got %v want CAPABILITIES", resp.GetType())
	}
	if got := resp.GetResult()["targets"]; got != "v1,v2" {
		t.Fatalf("result[targets]: got %q want %q", got, "v1,v2")
	}
	if got := resp.GetResult()["adapter"]; got != "command" {
		t.Fatalf("result[adapter]: got %q want %q", got, "command")
	}
	if got := resp.GetResult()["max_concurrency"]; got != "3" {
		t.Fatalf("result[max_concurrency]: got %q want %q", got, "3")
	}
	if got := resp.GetResult()["provider_status"]; got != "unknown" {
		t.Fatalf("result[provider_status]: got %q want %q", got, "unknown")
	}
	if got := resp.GetResult()["capacity"]; got != "3" {
		t.Fatalf("result[capacity]: got %q want %q", got, "3")
	}
	if got := resp.GetResult()["in_flight"]; got != "0" {
		t.Fatalf("result[in_flight]: got %q want %q", got, "0")
	}
	if got := resp.GetResult()["queued"]; got != "0" {
		t.Fatalf("result[queued]: got %q want %q", got, "0")
	}
	if len(resp.GetProviderSnapshots()) != 1 {
		t.Fatalf("expected 1 provider snapshot, got %d", len(resp.GetProviderSnapshots()))
	}
	snap := resp.GetProviderSnapshots()[0]
	if snap.GetAdapter() != "command" {
		t.Fatalf("snap.Adapter: got %q want %q", snap.GetAdapter(), "command")
	}
	if snap.GetStatus() != "unknown" {
		t.Fatalf("snap.Status: got %q want %q", snap.GetStatus(), "unknown")
	}
	if snap.GetCapacity() != 3 {
		t.Fatalf("snap.Capacity: got %d want %d", snap.GetCapacity(), 3)
	}
	if snap.GetInFlight() != 0 {
		t.Fatalf("snap.InFlight: got %d want %d", snap.GetInFlight(), 0)
	}
	if snap.GetQueued() != 0 {
		t.Fatalf("snap.Queued: got %d want %d", snap.GetQueued(), 0)
	}
	// Capabilities must not go through the adapter CommandHandler.
	if ca.lastReq.RequestID != "" {
		t.Fatalf("expected CommandHandler to be skipped, got %+v", ca.lastReq)
	}
}

func TestOnCommandRequest_Capabilities_InFlight(t *testing.T) {
	ba := newBlockingAdapter()
	router := &fixedRouter{adapterName: "blocking", adapters: make(map[string]runtime.Adapter)}
	router.adapters["blocking"] = ba
	n, _ := makeNode(t, router)

	// Start a background run which will block.
	err := n.OnRunRequest(context.Background(), &transport.Session{}, &iop.RunRequest{
		RunId:      "run-in-flight",
		Adapter:    "blocking",
		Target:     "v1",
		Background: true,
	})
	if err != nil {
		t.Fatalf("OnRunRequest: %v", err)
	}

	select {
	case <-ba.started:
	case <-time.After(2 * time.Second):
		t.Fatal("adapter never started")
	}

	// Now query Capabilities while the run is active.
	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-cap",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
		Adapter:   "blocking",
		Target:    "v1",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error, got %q", resp.GetError())
	}

	// Verify capacity, in_flight, queued values.
	if got := resp.GetResult()["capacity"]; got != "0" {
		t.Fatalf("result[capacity]: got %q want %q", got, "0")
	}
	if got := resp.GetResult()["in_flight"]; got != "1" {
		t.Fatalf("result[in_flight]: got %q want %q", got, "1")
	}
	if got := resp.GetResult()["queued"]; got != "0" {
		t.Fatalf("result[queued]: got %q want %q", got, "0")
	}

	if len(resp.GetProviderSnapshots()) != 1 {
		t.Fatalf("expected 1 provider snapshot, got %d", len(resp.GetProviderSnapshots()))
	}
	snap := resp.GetProviderSnapshots()[0]
	if snap.GetAdapter() != "blocking" {
		t.Fatalf("snap.Adapter: got %q want %q", snap.GetAdapter(), "blocking")
	}
	if snap.GetStatus() != "unknown" {
		t.Fatalf("snap.Status: got %q want %q", snap.GetStatus(), "unknown")
	}
	if snap.GetCapacity() != 0 {
		t.Fatalf("snap.Capacity: got %d want %d", snap.GetCapacity(), 0)
	}
	if snap.GetInFlight() != 1 {
		t.Fatalf("snap.InFlight: got %d want %d", snap.GetInFlight(), 1)
	}
	if snap.GetQueued() != 0 {
		t.Fatalf("snap.Queued: got %d want %d", snap.GetQueued(), 0)
	}

	// Clean up.
	if err := n.OnCancel(context.Background(), &transport.Session{}, &iop.CancelRequest{RunId: "run-in-flight"}); err != nil {
		t.Fatalf("OnCancel: %v", err)
	}
	<-ba.done
}

func TestOnCommandRequest_Capabilities_SafetyRejected(t *testing.T) {
	sa := newQueuedSlowAdapter("slow", 1, 4, 0)
	router := &fixedRouter{adapterName: "slow", adapters: map[string]runtime.Adapter{"slow": sa}}
	n, _ := makeNodeWithConcurrency(t, router, 0)

	if err := n.OnRunRequest(context.Background(), &transport.Session{}, &iop.RunRequest{
		RunId: "cap-hold", Adapter: "slow", Target: "v1", Background: true,
	}); err != nil {
		t.Fatalf("holding run: %v", err)
	}
	waitStarted(t, sa, "cap-hold")

	// The second run request must fail immediately since there is no queue.
	err := n.OnRunRequest(context.Background(), &transport.Session{}, &iop.RunRequest{
		RunId: "cap-rejected", Adapter: "slow", Target: "v1", Background: true,
	})
	if !errors.Is(err, node.ErrConcurrencyLimitExceeded) {
		t.Fatalf("expected ErrConcurrencyLimitExceeded, got %v", err)
	}

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-cap-queued",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
		Adapter:   "slow",
		Target:    "v1",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error, got %q", resp.GetError())
	}
	if got := resp.GetResult()["capacity"]; got != "1" {
		t.Fatalf("result[capacity]: got %q want %q", got, "1")
	}
	if got := resp.GetResult()["in_flight"]; got != "1" {
		t.Fatalf("result[in_flight]: got %q want %q", got, "1")
	}
	if got := resp.GetResult()["queued"]; got != "0" {
		t.Fatalf("result[queued]: got %q want %q", got, "0")
	}
	if len(resp.GetProviderSnapshots()) != 1 {
		t.Fatalf("expected 1 provider snapshot, got %d", len(resp.GetProviderSnapshots()))
	}
	snap := resp.GetProviderSnapshots()[0]
	if snap.GetCapacity() != 1 {
		t.Fatalf("snap.Capacity: got %d want %d", snap.GetCapacity(), 1)
	}
	if snap.GetInFlight() != 1 {
		t.Fatalf("snap.InFlight: got %d want %d", snap.GetInFlight(), 1)
	}
	if snap.GetQueued() != 0 {
		t.Fatalf("snap.Queued: got %d want %d", snap.GetQueued(), 0)
	}

	sa.releaseRun("cap-hold")
}

func TestOnCommandRequest_CapabilitiesProviderStatusModel(t *testing.T) {
	cases := []struct {
		name   string
		status runtime.ProviderStatus
		want   string
	}{
		{name: "available", status: runtime.ProviderStatusAvailable, want: "available"},
		{name: "unavailable", status: runtime.ProviderStatusUnavailable, want: "unavailable"},
		{name: "invalid status folds to unknown", status: runtime.ProviderStatus("degraded"), want: "unknown"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			ca := &commandAdapter{providerStatus: tc.status}
			router := &fixedRouter{adapterName: "command", adapters: make(map[string]runtime.Adapter)}
			router.adapters["command"] = ca
			n, _ := makeNode(t, router)

			resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
				RequestId: "req-cap-status",
				Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
				Adapter:   "command",
				Target:    "codex",
			})
			if err != nil {
				t.Fatalf("OnCommandRequest: %v", err)
			}
			if resp.GetError() != "" {
				t.Fatalf("expected no error, got %q", resp.GetError())
			}
			if got := resp.GetResult()["provider_status"]; got != tc.want {
				t.Fatalf("result[provider_status]: got %q want %q", got, tc.want)
			}
		})
	}
}

func TestOnCommandRequest_CapabilitiesWithProber(t *testing.T) {
	t.Run("probe_target_hit", func(t *testing.T) {
		pa := &proberTestAdapter{
			providerStatus: runtime.ProviderStatusAvailable,
			probeTargets:   []string{"model-a", "model-b"},
		}
		router := &fixedRouter{adapterName: "prober", adapters: make(map[string]runtime.Adapter)}
		router.adapters["prober"] = pa
		n, _ := makeNode(t, router)

		resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
			RequestId: "req-cap-prober",
			Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
			Adapter:   "prober",
			Target:    "model-a",
		})
		if err != nil {
			t.Fatalf("OnCommandRequest: %v", err)
		}
		if resp.GetError() != "" {
			t.Fatalf("expected no error, got %q", resp.GetError())
		}
		if got := resp.GetResult()["provider_status"]; got != "available" {
			t.Fatalf("result[provider_status]: got %q want %q", got, "available")
		}
		if got := resp.GetResult()["targets"]; got != "model-a,model-b" {
			t.Fatalf("result[targets]: got %q want %q", got, "model-a,model-b")
		}
	})

	t.Run("probe_target_miss", func(t *testing.T) {
		pa := &proberTestAdapter{
			providerStatus: runtime.ProviderStatusAvailable,
			probeTargets:   []string{"model-a"},
		}
		router := &fixedRouter{adapterName: "prober", adapters: make(map[string]runtime.Adapter)}
		router.adapters["prober"] = pa
		n, _ := makeNode(t, router)

		resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
			RequestId: "req-cap-prober",
			Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
			Adapter:   "prober",
			Target:    "model-c",
		})
		if err != nil {
			t.Fatalf("OnCommandRequest: %v", err)
		}
		if resp.GetError() != "" {
			t.Fatalf("expected no error, got %q", resp.GetError())
		}
		if got := resp.GetResult()["provider_status"]; got != "unavailable" {
			t.Fatalf("result[provider_status]: got %q want %q", got, "unavailable")
		}
	})

	t.Run("probe_error_folds_to_unavailable", func(t *testing.T) {
		pa := &proberTestAdapter{
			providerStatus: runtime.ProviderStatusAvailable,
			probeErr:       fmt.Errorf("network error"),
		}
		router := &fixedRouter{adapterName: "prober", adapters: make(map[string]runtime.Adapter)}
		router.adapters["prober"] = pa
		n, _ := makeNode(t, router)

		resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
			RequestId: "req-cap-prober",
			Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
			Adapter:   "prober",
			Target:    "model-a",
		})
		if err != nil {
			t.Fatalf("OnCommandRequest: %v", err)
		}
		// Probe error should NOT cause a command error, but set provider_status to unavailable
		if resp.GetError() != "" {
			t.Fatalf("expected no error, got %q", resp.GetError())
		}
		if got := resp.GetResult()["provider_status"]; got != "unavailable" {
			t.Fatalf("result[provider_status]: got %q want %q", got, "unavailable")
		}
		if got := resp.GetResult()["provider_detail"]; got != "network error" {
			t.Fatalf("result[provider_detail]: got %q want %q", got, "network error")
		}
	})
}

// TestOnCommandRequest_CapabilitiesWithoutCommandHandler verifies CAPABILITIES
// succeeds for an adapter that does not implement runtime.CommandHandler.
func TestOnCommandRequest_CapabilitiesWithoutCommandHandler(t *testing.T) {
	ta := &terminatingAdapter{} // no CommandHandler
	router := &fixedRouter{adapterName: "terminating", adapters: make(map[string]runtime.Adapter)}
	router.adapters["terminating"] = ta
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-cap",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
		Adapter:   "terminating",
		Target:    "codex",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error, got %q", resp.GetError())
	}
	if got := resp.GetResult()["adapter"]; got != "terminating" {
		t.Fatalf("result[adapter]: got %q want %q", got, "terminating")
	}
}

func TestOnCommandRequest_TransportStatus(t *testing.T) {
	ta := &terminatingAdapter{} // adapter not required for transport_status
	router := &fixedRouter{adapterName: "terminating", adapters: make(map[string]runtime.Adapter)}
	router.adapters["terminating"] = ta
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-tx",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_TRANSPORT_STATUS,
		Adapter:   "terminating",
		Target:    "codex",
		SessionId: "sess-a",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error, got %q", resp.GetError())
	}
	if got := resp.GetResult()["node_id"]; got != "test-node" {
		t.Fatalf("result[node_id]: got %q want %q", got, "test-node")
	}
	if got := resp.GetResult()["adapter"]; got != "terminating" {
		t.Fatalf("result[adapter]: got %q want %q", got, "terminating")
	}
	if got := resp.GetResult()["session_id"]; got != "sess-a" {
		t.Fatalf("result[session_id]: got %q want %q", got, "sess-a")
	}
	if _, ok := resp.GetResult()["connected"]; !ok {
		t.Fatalf("result missing connected key: %+v", resp.GetResult())
	}
	if got, ok := resp.GetResult()["state"]; !ok || (got != "connected" && got != "disconnected") {
		t.Fatalf("result[state]: got %q, want \"connected\" or \"disconnected\"", got)
	}
}

func TestOnCommandRequest_AdapterError(t *testing.T) {
	ca := &commandAdapter{}
	router := &fixedRouter{adapterName: "command", adapters: make(map[string]runtime.Adapter)}
	router.adapters["command"] = ca
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-1",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_SESSION_LIST,
		Adapter:   "command",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if !strings.Contains(resp.GetError(), "command not supported by adapter") {
		t.Fatalf("expected adapter unsupported error, got %q", resp.GetError())
	}
}

func TestOnCommandRequest_TransportStatusDefaultsSessionID(t *testing.T) {
	ta := &terminatingAdapter{}
	router := &fixedRouter{adapterName: "terminating", adapters: make(map[string]runtime.Adapter)}
	router.adapters["terminating"] = ta
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-ts-default",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_TRANSPORT_STATUS,
		Adapter:   "terminating",
		Target:    "codex",
		SessionId: "",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error, got %q", resp.GetError())
	}
	if got := resp.GetSessionId(); got != "default" {
		t.Fatalf("response SessionId: got %q want %q", got, "default")
	}
	if got := resp.GetResult()["session_id"]; got != "default" {
		t.Fatalf("result[session_id]: got %q want %q", got, "default")
	}
	if got, ok := resp.GetResult()["state"]; !ok || (got != "connected" && got != "disconnected") {
		t.Fatalf("result[state]: got %q, want \"connected\" or \"disconnected\"", got)
	}
}

func TestOnCommandRequest_ErrorResponsesPreserveEnvelope(t *testing.T) {
	cases := []struct {
		name    string
		req     *iop.NodeCommandRequest
		adapter runtime.Adapter
		wantErr string
	}{
		{
			name: "missing adapter",
			req: &iop.NodeCommandRequest{
				RequestId: "req-missing",
				Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_USAGE_STATUS,
				Adapter:   "missing",
				Target:    "tgt",
				SessionId: "sess-x",
			},
			adapter: nil,
			wantErr: "not found",
		},
		{
			name: "unsupported command type",
			req: &iop.NodeCommandRequest{
				RequestId: "req-unspec",
				Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_UNSPECIFIED,
				Adapter:   "terminating",
				Target:    "tgt",
				SessionId: "sess-y",
			},
			adapter: &terminatingAdapter{},
			wantErr: "unsupported command type",
		},
		{
			name: "adapter does not support commands",
			req: &iop.NodeCommandRequest{
				RequestId: "req-nosupport",
				Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_USAGE_STATUS,
				Adapter:   "terminating",
				Target:    "tgt",
				SessionId: "sess-z",
			},
			adapter: &terminatingAdapter{},
			wantErr: "does not support commands",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			adapters := make(map[string]runtime.Adapter)
			if tc.adapter != nil {
				adapters[tc.req.Adapter] = tc.adapter
			}
			router := &fixedRouter{adapterName: tc.req.Adapter, adapters: adapters}
			n, _ := makeNode(t, router)

			resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, tc.req)
			if err != nil {
				t.Fatalf("OnCommandRequest: %v", err)
			}
			if !strings.Contains(resp.GetError(), tc.wantErr) {
				t.Fatalf("error: got %q want contains %q", resp.GetError(), tc.wantErr)
			}
			if got := resp.GetRequestId(); got != tc.req.RequestId {
				t.Fatalf("RequestId: got %q want %q", got, tc.req.RequestId)
			}
			if got := resp.GetType(); got != tc.req.Type {
				t.Fatalf("Type: got %v want %v", got, tc.req.Type)
			}
			if got := resp.GetAdapter(); got != tc.req.Adapter {
				t.Fatalf("Adapter: got %q want %q", got, tc.req.Adapter)
			}
			if got := resp.GetTarget(); got != tc.req.Target {
				t.Fatalf("Target: got %q want %q", got, tc.req.Target)
			}
			if got := resp.GetSessionId(); got != tc.req.SessionId {
				t.Fatalf("SessionId: got %q want %q", got, tc.req.SessionId)
			}
		})
	}
}

// --- LookupAdapter / ambiguous error propagation tests ---

func TestOnCancel_TerminateSession_AmbiguousAdapterError(t *testing.T) {
	ambigErr := fmt.Errorf("adapter \"ollama\" is ambiguous: matches instance keys [ollama@local ollama@dgx]; use an instance key")
	router := &fixedRouter{
		adapterName:  "ollama",
		adapters:     make(map[string]runtime.Adapter),
		lookupErrors: map[string]error{"ollama": ambigErr},
	}
	n, _ := makeNode(t, router)

	err := n.OnCancel(context.Background(), &transport.Session{}, &iop.CancelRequest{
		Adapter:   "ollama",
		Target:    "llama3",
		SessionId: "sess-1",
		Action:    iop.CancelAction_CANCEL_ACTION_TERMINATE_SESSION,
	})
	if err == nil {
		t.Fatal("expected error for ambiguous adapter lookup")
	}
	if !strings.Contains(err.Error(), "ambiguous") {
		t.Errorf("expected 'ambiguous' in error, got %v", err)
	}
	if !strings.Contains(err.Error(), "instance key") {
		t.Errorf("expected 'instance key' guidance in error, got %v", err)
	}
}

func TestOnCancel_TerminateSession_ExactInstanceKey(t *testing.T) {
	ta := &terminatingAdapter{}
	router := &fixedRouter{
		adapterName: "ollama@local",
		adapters:    map[string]runtime.Adapter{"ollama@local": ta},
	}
	n, _ := makeNode(t, router)

	err := n.OnCancel(context.Background(), &transport.Session{}, &iop.CancelRequest{
		Adapter:   "ollama@local",
		Target:    "llama3",
		SessionId: "sess-a",
		Action:    iop.CancelAction_CANCEL_ACTION_TERMINATE_SESSION,
	})
	if err != nil {
		t.Fatalf("OnCancel with exact instance key: %v", err)
	}
	if atomic.LoadInt32(&ta.terminateCalls) != 1 {
		t.Fatal("expected TerminateSession to be called once")
	}
	if ta.lastTarget != "llama3" || ta.lastSessionID != "sess-a" {
		t.Errorf("terminate args: target=%q session=%q", ta.lastTarget, ta.lastSessionID)
	}
}

func TestOnCommandRequest_Capabilities_AmbiguousAdapter(t *testing.T) {
	ambigErr := fmt.Errorf("adapter \"cli\" is ambiguous: matches instance keys [cli@claude cli@codex]; use an instance key")
	router := &fixedRouter{
		adapterName:  "cli",
		adapters:     make(map[string]runtime.Adapter),
		lookupErrors: map[string]error{"cli": ambigErr},
	}
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-ambig-cap",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
		Adapter:   "cli",
		Target:    "codex",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if !strings.Contains(resp.GetError(), "ambiguous") {
		t.Errorf("expected 'ambiguous' in response error, got %q", resp.GetError())
	}
	if !strings.Contains(resp.GetError(), "instance key") {
		t.Errorf("expected 'instance key' guidance in response error, got %q", resp.GetError())
	}
}

func TestOnCommandRequest_AdapterDispatch_AmbiguousAdapter(t *testing.T) {
	ambigErr := fmt.Errorf("adapter \"cli\" is ambiguous: matches instance keys [cli@claude cli@codex]; use an instance key")
	router := &fixedRouter{
		adapterName:  "cli",
		adapters:     make(map[string]runtime.Adapter),
		lookupErrors: map[string]error{"cli": ambigErr},
	}
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-ambig-cmd",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_USAGE_STATUS,
		Adapter:   "cli",
		Target:    "codex",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if !strings.Contains(resp.GetError(), "ambiguous") {
		t.Errorf("expected 'ambiguous' in response error, got %q", resp.GetError())
	}
}

func TestOnCommandRequest_MultiAdapterCapabilities(t *testing.T) {
	ika1 := &instanceKeyAdapter{instanceKey: "ollama@local"}
	ika2 := &instanceKeyAdapter{instanceKey: "ollama@dgx"}
	router := &fixedRouter{
		adapters: map[string]runtime.Adapter{
			"ollama@local": ika1,
			"ollama@dgx":   ika2,
		},
	}
	n, _ := makeNode(t, router)

	cases := []struct {
		adapter     string
		wantInstKey string
	}{
		{"ollama@local", "ollama@local"},
		{"ollama@dgx", "ollama@dgx"},
	}
	instKeys := make([]string, 0, len(cases))
	for _, tc := range cases {
		resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
			RequestId: "req-multi-" + tc.adapter,
			Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
			Adapter:   tc.adapter,
			Target:    "llama3",
		})
		if err != nil {
			t.Fatalf("CAPABILITIES %s: %v", tc.adapter, err)
		}
		if resp.GetError() != "" {
			t.Fatalf("CAPABILITIES %s: unexpected error %q", tc.adapter, resp.GetError())
		}
		if got := resp.GetResult()["instance_key"]; got != tc.wantInstKey {
			t.Errorf("CAPABILITIES %s: instance_key got %q want %q", tc.adapter, got, tc.wantInstKey)
		}
		if resp.GetAdapter() != tc.adapter {
			t.Errorf("CAPABILITIES %s: response adapter got %q want %q", tc.adapter, resp.GetAdapter(), tc.adapter)
		}
		instKeys = append(instKeys, resp.GetResult()["instance_key"])
	}
	if instKeys[0] == instKeys[1] {
		t.Errorf("expected distinct instance_key for distinct adapters, both got %q", instKeys[0])
	}
}

func TestOnCommandRequest_Capabilities_ExactInstanceKey(t *testing.T) {
	ika := &instanceKeyAdapter{instanceKey: "ollama@local"}
	router := &fixedRouter{
		adapterName: "ollama@local",
		adapters:    map[string]runtime.Adapter{"ollama@local": ika},
	}
	n, _ := makeNode(t, router)

	resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
		RequestId: "req-exact-cap",
		Type:      iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
		Adapter:   "ollama@local",
		Target:    "llama3",
	})
	if err != nil {
		t.Fatalf("OnCommandRequest: %v", err)
	}
	if resp.GetError() != "" {
		t.Fatalf("expected no error for exact instance key, got %q", resp.GetError())
	}
	if got := resp.GetResult()["adapter"]; got != "ollama" {
		t.Errorf("result[adapter]: got %q want ollama", got)
	}
	if got := resp.GetResult()["instance_key"]; got != "ollama@local" {
		t.Errorf("result[instance_key]: got %q want ollama@local", got)
	}
}
