package service

import (
	"context"
	"net"
	"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 TestSubmitRunProviderPoolRewritesAdapterAndTarget(t *testing.T) {
	edgeConn, nodeConn := net.Pipe()
	defer edgeConn.Close()
	defer 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)

	capturedReq := make(chan *iop.RunRequest, 1)
	toki.AddListenerTyped[*iop.RunRequest](&nodeClient.Communicator, func(req *iop.RunRequest) {
		capturedReq <- proto.Clone(req).(*iop.RunRequest)
	})

	catalog := []config.ModelCatalogEntry{
		{
			ID: "qwen3.6:35b",
			Providers: map[string]string{
				"prov-vllm-01": "served-qwen",
			},
		},
	}

	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",
				Models:                 []string{"served-qwen"},
				Health:                 "available",
				Capacity:               2,
				ResponseStallTimeoutMS: 45000,
			},
		},
	})

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

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

	result, err := svc.SubmitRun(context.Background(), SubmitRunRequest{
		RunID:         "run-pool-test-001",
		ModelGroupKey: "qwen3.6:35b",
		ProviderPool:  true,
		Background:    true,
	})
	if err != nil {
		t.Fatalf("SubmitRun: %v", err)
	}
	if result == nil {
		t.Fatal("expected non-nil RunResult")
	}

	select {
	case got := <-capturedReq:
		if got.GetAdapter() != "vllm-gpu" || got.GetTarget() != "served-qwen" || got.GetRunId() != "run-pool-test-001" || got.GetResponseStallTimeoutMs() != 45000 {
			t.Fatalf("unexpected RunRequest: %+v", got)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("no RunRequest captured from fake node; SubmitRun did not send")
	}
}

// TestResolveProviderPoolCandidatesAdapterInstanceValidation verifies that the
// defensive isProviderAdapterInstanceValid check in resolveProviderPoolCandidates
// excludes providers whose adapter name resolves to a disabled exact instance,
// an ambiguous type route, a zero-instance type route, or an unknown adapter key,
// while valid named instances and single enabled type routes remain as candidates.
func TestResolveProviderPoolCandidatesAdapterInstanceValidation(t *testing.T) {
	catalog := []config.ModelCatalogEntry{
		{
			ID: "ai-group",
			Providers: map[string]string{
				"prov-disabled":  "ai-model",
				"prov-ambiguous": "ai-model",
				"prov-valid":     "ai-model",
				"prov-zero-type": "ai-model",
				"prov-missing":   "ai-model",
			},
		},
	}

	store := edgenode.NewNodeStore()

	// prov-disabled: adapter="vllm-gpu" but VllmInstance "vllm-gpu" is disabled.
	store.Add(&edgenode.NodeRecord{
		ID: "node-disabled",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "vllm-gpu", Enabled: false, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-disabled", Adapter: "vllm-gpu", Models: []string{"ai-model"}, Health: "available", Capacity: 2},
		},
	})

	// prov-ambiguous: adapter="openai_compat" type route with 2 enabled instances → ambiguous.
	store.Add(&edgenode.NodeRecord{
		ID: "node-ambiguous",
		Adapters: config.AdaptersConf{
			OpenAICompatInstances: []config.OpenAICompatInstanceConf{
				{Name: "compat-a", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
				{Name: "compat-b", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-ambiguous", Adapter: "openai_compat", Models: []string{"ai-model"}, Health: "available", Capacity: 2},
		},
	})

	// prov-valid: adapter="vllm-gpu" and VllmInstance "vllm-gpu" is enabled.
	store.Add(&edgenode.NodeRecord{
		ID: "node-valid",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-valid", Adapter: "vllm-gpu", Models: []string{"ai-model"}, Health: "available", Capacity: 2},
		},
	})

	// prov-zero-type: adapter="ollama" type route but no enabled ollama instances → zero-instance type route excluded.
	store.Add(&edgenode.NodeRecord{
		ID: "node-zero-type",
		Adapters: config.AdaptersConf{
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama-disabled", Enabled: false, BaseURL: "http://127.0.0.1:11434"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-zero-type", Adapter: "ollama", Models: []string{"ai-model"}, Health: "available", Capacity: 2},
		},
	})

	// prov-missing: adapter="unknown-missing" not in any instance list and not a known type key → unknown adapter excluded.
	store.Add(&edgenode.NodeRecord{
		ID: "node-missing",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{
				{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:9000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-missing", Adapter: "unknown-missing", Models: []string{"ai-model"}, Health: "available", Capacity: 2},
		},
	})

	reg := edgenode.NewRegistry()
	allRecs := store.All()
	for _, rec := range allRecs {
		reg.Register(&edgenode.NodeEntry{NodeID: rec.ID, LifecycleState: edgenode.LifecycleConnected})
	}

	svc := New(reg, nil)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	req := SubmitRunRequest{ModelGroupKey: "ai-group", ProviderPool: true}
	storeSnap, catalogSnap, _ := svc.runtimeConfigSnapshot()
	candidates, _, err := svc.resolveProviderPoolCandidates(req, storeSnap, catalogSnap)
	if err != nil {
		t.Fatalf("resolveProviderPoolCandidates: %v", err)
	}

	// Only prov-valid should be a candidate.
	got := make(map[string]bool)
	for _, c := range candidates {
		got[c.providerID] = true
	}
	if len(candidates) != 1 {
		t.Fatalf("expected 1 candidate (prov-valid), got %d: %v", len(candidates), got)
	}
	if !got["prov-valid"] {
		t.Error("prov-valid (enabled exact instance) must be a candidate")
	}
	if got["prov-disabled"] {
		t.Error("prov-disabled (disabled exact instance) must be excluded")
	}
	if got["prov-ambiguous"] {
		t.Error("prov-ambiguous (ambiguous type route) must be excluded")
	}
	if got["prov-zero-type"] {
		t.Error("prov-zero-type (zero-instance type route) must be excluded")
	}
	if got["prov-missing"] {
		t.Error("prov-missing (unknown adapter) must be excluded")
	}
}

// TestGetSnapshotForNodeCatalogFirstNoDuplicates verifies that when a node has
// both adapter instances and a providers[] catalog, getSnapshotForNode returns
// only catalog snapshots (no adapter duplicates).
func TestGetSnapshotForNodeCatalogFirstNoDuplicates(t *testing.T) {
	rec := &edgenode.NodeRecord{
		ID: "node-catalog",
		Adapters: config.AdaptersConf{
			Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"},
			OpenAICompatInstances: []config.OpenAICompatInstanceConf{
				{Name: "vllm-gpu", Enabled: true},
			},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-a", Adapter: "vllm-gpu", Models: []string{"model-a"}, Health: "available", Capacity: 2},
			{ID: "prov-b", Adapter: "vllm-gpu", Models: []string{"model-b"}, Health: "available", Capacity: 3},
		},
		Runtime: config.RuntimeConf{Concurrency: 1},
	}
	store := edgenode.NewNodeStore()
	store.Add(rec)

	m := newModelQueueManager(store)

	snaps := m.getSnapshotForNode("node-catalog", rec, true)

	// Must return exactly the catalog providers — no CLI or openai_compat adapter duplicates.
	if len(snaps) != 2 {
		t.Fatalf("expected 2 catalog snapshots, got %d: %+v", len(snaps), snaps)
	}
	ids := map[string]bool{}
	for _, s := range snaps {
		ids[s.Id] = true
	}
	if !ids["prov-a"] || !ids["prov-b"] {
		t.Errorf("expected catalog provider IDs prov-a and prov-b, got: %v", ids)
	}
	// No adapter-only snapshots (no empty Id entries that correspond to adapter sections).
	for _, s := range snaps {
		if s.Id == "" {
			t.Errorf("unexpected adapter-only snapshot (empty Id) in catalog-first result: %+v", s)
		}
	}
}

// TestResolveProviderPoolCandidatesSkipsDisabledProvider verifies that a
// provider with enabled=false is excluded from dispatch candidates.
func TestResolveProviderPoolCandidatesSkipsDisabledProvider(t *testing.T) {
	disabled := false
	catalog := []config.ModelCatalogEntry{
		{
			ID: "model-x",
			Providers: map[string]string{
				"prov-active":   "served-x",
				"prov-disabled": "served-x",
			},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "node-d",
		Providers: []config.NodeProviderConf{
			{ID: "prov-active", Type: "vllm", Adapter: "vllm-gpu", Models: []string{"served-x"}, Health: "available", Capacity: 2},
			{ID: "prov-disabled", Type: "vllm", Adapter: "vllm-gpu", Models: []string{"served-x"}, Health: "available", Capacity: 2, Enabled: &disabled},
		},
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"}},
		},
	})
	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{NodeID: "node-d", LifecycleState: edgenode.LifecycleConnected})

	svc := New(reg, nil)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	storeSnap, catalogSnap, _ := svc.runtimeConfigSnapshot()
	candidates, _, err := svc.resolveProviderPoolCandidates(SubmitRunRequest{ModelGroupKey: "model-x", ProviderPool: true}, storeSnap, catalogSnap)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(candidates) != 1 {
		t.Fatalf("expected 1 candidate (disabled filtered), got %d", len(candidates))
	}
	if candidates[0].providerID != "prov-active" {
		t.Errorf("expected prov-active, got %q", candidates[0].providerID)
	}
}

// TestResolveProviderPoolCandidatesUsesProviderIDWhenAdapterOmitted verifies
// that a provider-first provider (empty Adapter field) uses its ID as the
// dispatch adapter key.
func TestResolveProviderPoolCandidatesUsesProviderIDWhenAdapterOmitted(t *testing.T) {
	catalog := []config.ModelCatalogEntry{
		{
			ID: "model-y",
			Providers: map[string]string{
				"prov-first": "served-y",
			},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(&edgenode.NodeRecord{
		ID: "node-pf",
		Providers: []config.NodeProviderConf{
			{ID: "prov-first", Type: "vllm", Adapter: "", Models: []string{"served-y"}, Health: "available", Capacity: 3},
		},
	})
	reg := edgenode.NewRegistry()
	reg.Register(&edgenode.NodeEntry{NodeID: "node-pf", LifecycleState: edgenode.LifecycleConnected})

	svc := New(reg, nil)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	storeSnap, catalogSnap, _ := svc.runtimeConfigSnapshot()
	candidates, _, err := svc.resolveProviderPoolCandidates(SubmitRunRequest{ModelGroupKey: "model-y", ProviderPool: true}, storeSnap, catalogSnap)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(candidates) != 1 {
		t.Fatalf("expected 1 candidate, got %d", len(candidates))
	}
	if candidates[0].adapter != "prov-first" {
		t.Errorf("provider-first adapter key: got %q, want provider ID %q", candidates[0].adapter, "prov-first")
	}
	if candidates[0].providerID != "prov-first" {
		t.Errorf("providerID: got %q", candidates[0].providerID)
	}
}

// TestProviderSnapshotsReportDisabledProvider verifies that a disabled
// provider appears in snapshots with status=disabled and effective capacity 0.
func TestProviderSnapshotsReportDisabledProvider(t *testing.T) {
	disabled := false
	rec := &edgenode.NodeRecord{
		ID: "node-snap",
		Providers: []config.NodeProviderConf{
			{ID: "prov-on", Type: "vllm", Health: "available", Capacity: 4},
			{ID: "prov-off", Type: "vllm", Health: "available", Capacity: 4, Enabled: &disabled},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(rec)
	m := newModelQueueManager(store)

	snaps := m.getSnapshotForNode("node-snap", rec, true)
	if len(snaps) != 2 {
		t.Fatalf("expected 2 snapshots, got %d", len(snaps))
	}

	byID := map[string]*iop.ProviderSnapshot{}
	for _, s := range snaps {
		byID[s.Id] = s
	}

	on := byID["prov-on"]
	if on == nil {
		t.Fatal("prov-on snapshot missing")
	}
	if on.Status != "available" {
		t.Errorf("prov-on status: got %q, want available", on.Status)
	}
	if on.Capacity != 4 {
		t.Errorf("prov-on capacity: got %d, want 4", on.Capacity)
	}

	off := byID["prov-off"]
	if off == nil {
		t.Fatal("prov-off snapshot missing")
	}
	if off.Status != "disabled" {
		t.Errorf("prov-off status: got %q, want disabled", off.Status)
	}
	if off.Health != "disabled" {
		t.Errorf("prov-off health: got %q, want disabled", off.Health)
	}
	if off.Capacity != 0 {
		t.Errorf("prov-off effective capacity: got %d, want 0", off.Capacity)
	}
}

// TestStatusProviderProviderFirstNoAdapterDuplicates verifies that provider-first
// providers (empty Adapter) appear once in snapshots with no adapter duplicate.
func TestStatusProviderProviderFirstNoAdapterDuplicates(t *testing.T) {
	rec := &edgenode.NodeRecord{
		ID: "node-pf-snap",
		Adapters: config.AdaptersConf{
			VllmInstances: []config.VllmInstanceConf{{Name: "vllm-gpu", Enabled: true}},
		},
		Providers: []config.NodeProviderConf{
			{ID: "prov-first-a", Type: "vllm", Adapter: "", Models: []string{"model-a"}, Health: "available", Capacity: 2},
			{ID: "prov-first-b", Type: "vllm", Adapter: "", Models: []string{"model-b"}, Health: "available", Capacity: 2},
		},
	}
	store := edgenode.NewNodeStore()
	store.Add(rec)
	m := newModelQueueManager(store)

	snaps := m.getSnapshotForNode("node-pf-snap", rec, true)
	// catalog-first: exactly 2 provider snapshots, no adapter duplicate.
	if len(snaps) != 2 {
		t.Fatalf("expected 2 provider-first snapshots, got %d: %+v", len(snaps), snaps)
	}
	for _, s := range snaps {
		if s.Id == "" {
			t.Errorf("unexpected adapter-only snapshot (empty Id): %+v", s)
		}
		if s.Adapter != "" {
			t.Errorf("provider-first snapshot should have empty Adapter, got %q for id=%q", s.Adapter, s.Id)
		}
	}
}

// TestResolveProviderPoolCandidatesPropagatesPriority verifies that the actual
// resolveProviderPoolCandidates path propagates config provider Priority into
// candidateNode.priority.
func TestResolveProviderPoolCandidatesPropagatesPriority(t *testing.T) {
	catalog := []config.ModelCatalogEntry{
		{
			ID: "model-prio",
			Providers: map[string]string{
				"prov-high-prio":   "served-model-prio",
				"prov-low-prio":    "served-model-prio",
				"prov-no-priority": "served-model-prio",
			},
		},
	}

	store := edgenode.NewNodeStore()
	// High-priority provider (priority=3).
	store.Add(&edgenode.NodeRecord{
		ID:      "node-prio",
		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-high-prio",
				Adapter:  "vllm-gpu",
				Models:   []string{"served-model-prio"},
				Health:   "available",
				Capacity: 2,
				Priority: 3,
			},
			{
				ID:       "prov-low-prio",
				Adapter:  "vllm-gpu",
				Models:   []string{"served-model-prio"},
				Health:   "available",
				Capacity: 2,
				Priority: 10,
			},
			{
				ID:       "prov-no-priority",
				Adapter:  "vllm-gpu",
				Models:   []string{"served-model-prio"},
				Health:   "available",
				Capacity: 2,
				// Priority omitted → defaults to 0.
			},
		},
	})

	reg := edgenode.NewRegistry()
	for _, rec := range store.All() {
		reg.Register(&edgenode.NodeEntry{NodeID: rec.ID, LifecycleState: edgenode.LifecycleConnected})
	}

	svc := New(reg, nil)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	storeSnap, catalogSnap, _ := svc.runtimeConfigSnapshot()
	candidates, _, err := svc.resolveProviderPoolCandidates(
		SubmitRunRequest{ModelGroupKey: "model-prio", ProviderPool: true},
		storeSnap, catalogSnap,
	)
	if err != nil {
		t.Fatalf("resolveProviderPoolCandidates: %v", err)
	}
	if len(candidates) != 3 {
		t.Fatalf("expected 3 candidates, got %d", len(candidates))
	}

	byID := map[string]*candidateNode{}
	for i := range candidates {
		byID[candidates[i].providerID] = &candidates[i]
	}

	// prov-high-prio: fixture Priority=3 → candidate.priority must be 3.
	hp := byID["prov-high-prio"]
	if hp == nil {
		t.Fatal("prov-high-prio not in candidates")
	}
	if hp.priority != 3 {
		t.Errorf("prov-high-prio: got priority %d, want 3", hp.priority)
	}

	// prov-low-prio: fixture Priority=10 → candidate.priority must be 10.
	lp := byID["prov-low-prio"]
	if lp == nil {
		t.Fatal("prov-low-prio not in candidates")
	}
	if lp.priority != 10 {
		t.Errorf("prov-low-prio: got priority %d, want 10", lp.priority)
	}

	// prov-no-priority: Priority omitted → defaults to 0.
	np := byID["prov-no-priority"]
	if np == nil {
		t.Fatal("prov-no-priority not in candidates")
	}
	if np.priority != 0 {
		t.Errorf("prov-no-priority: got priority %d, want 0", np.priority)
	}
}

// TestGetSnapshotForNodeAdapterFallback verifies that a node with no
// providers catalog returns snapshots from configured provider adapters.
func TestGetSnapshotForNodeAdapterFallback(t *testing.T) {
	rec := &edgenode.NodeRecord{
		ID: "node-legacy",
		Adapters: config.AdaptersConf{
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama-local", Enabled: true, Capacity: 3},
			},
		},
		Runtime: config.RuntimeConf{Concurrency: 2},
	}
	store := edgenode.NewNodeStore()
	store.Add(rec)

	m := newModelQueueManager(store)

	snaps := m.getSnapshotForNode("node-legacy", rec, true)

	if len(snaps) != 1 {
		t.Fatalf("expected 1 ollama-local adapter snapshot, got %d: %+v", len(snaps), snaps)
	}
	adapters := map[string]bool{}
	for _, s := range snaps {
		adapters[s.Adapter] = true
	}
	if !adapters["ollama-local"] {
		t.Error("expected ollama-local adapter snapshot in fallback")
	}
}

func TestResolveProviderPoolCandidatesClassifiesExecutionPath(t *testing.T) {
	catalog := []config.ModelCatalogEntry{
		{
			ID: "test-model",
			Providers: map[string]string{
				"prov-openai-compat":     "served-model",
				"prov-openai-api":        "served-model",
				"prov-vllm":              "served-model",
				"prov-vllm-mlx":          "served-model",
				"prov-lemonade":          "served-model",
				"prov-sglang":            "served-model",
				"prov-seulgivibe-claude": "served-model",
				"prov-seulgivibe-openai": "served-model",
				"prov-ollama":            "served-model",
				"prov-cli":               "served-model",
				"prov-unknown":           "served-model",
			},
		},
	}

	store := edgenode.NewNodeStore()

	// OpenAI-compatible providers (all should be tunnel path).
	store.Add(&edgenode.NodeRecord{
		ID:      "node-openai",
		Runtime: config.RuntimeConf{Concurrency: 4},
		Providers: []config.NodeProviderConf{
			{ID: "prov-openai-compat", Type: "openai_compat", Category: "api", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-openai-api", Type: "openai_api", Category: "api", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-vllm", Type: "vllm", Category: "local_inference", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-vllm-mlx", Type: "vllm-mlx", Category: "local_inference", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-lemonade", Type: "lemonade", Category: "local_inference", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-sglang", Type: "sglang", Category: "local_inference", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-seulgivibe-claude", Type: "seulgivibe_claude", Category: "api", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-seulgivibe-openai", Type: "seulgivibe_openai", Category: "api", Health: "available", Capacity: 2, Models: []string{"served-model"}},
		},
	})

	// Ollama and unknown providers use normalized execution.
	store.Add(&edgenode.NodeRecord{
		ID:       "node-normalized",
		Runtime:  config.RuntimeConf{Concurrency: 4},
		Adapters: config.AdaptersConf{OllamaInstances: []config.OllamaInstanceConf{{Name: "ollama", Enabled: true}}},
		Providers: []config.NodeProviderConf{
			{ID: "prov-ollama", Type: "ollama", Category: "local_inference", Adapter: "ollama", Health: "available", Capacity: 2, Models: []string{"served-model"}},
			{ID: "prov-unknown", Type: "unknown_type", Category: "api", Health: "available", Capacity: 2, Models: []string{"served-model"}},
		},
	})

	// Build a fake registry with all nodes.
	reg := edgenode.NewRegistry()
	allRecs := store.All()
	for _, rec := range allRecs {
		entry := &edgenode.NodeEntry{
			NodeID:         rec.ID,
			LifecycleState: edgenode.LifecycleConnected,
		}
		reg.Register(entry)
	}

	// Create Service with catalog and node store.
	svc := New(reg, nil)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	req := SubmitRunRequest{ModelGroupKey: "test-model", ProviderPool: true}
	candidates, _, err := svc.resolveProviderPoolCandidates(req, store, catalog)
	if err != nil {
		t.Fatalf("resolveProviderPoolCandidates: %v", err)
	}

	expectedTunnel := map[string]bool{
		"prov-openai-compat":     true,
		"prov-openai-api":        true,
		"prov-vllm":              true,
		"prov-vllm-mlx":          true,
		"prov-lemonade":          true,
		"prov-sglang":            true,
		"prov-seulgivibe-claude": true,
		"prov-seulgivibe-openai": true,
	}
	expectedNormalized := map[string]bool{
		"prov-ollama":  true,
		"prov-unknown": true,
	}

	// Convert returned candidates to a map.
	got := map[string]providerExecutionPath{}
	for _, c := range candidates {
		got[c.providerID] = c.executionPath
	}

	// Verify tunnel-path providers are present and classified as tunnel.
	for providerID := range expectedTunnel {
		path, ok := got[providerID]
		if !ok {
			t.Errorf("expected tunnel provider %q missing from candidates (got %d total)", providerID, len(candidates))
			continue
		}
		if path != providerExecutionPathTunnel {
			t.Errorf("provider %q: expected tunnel path, got %q", providerID, path)
		}
	}

	// Verify normalized-path providers are present and classified as normalized.
	for providerID := range expectedNormalized {
		path, ok := got[providerID]
		if !ok {
			t.Errorf("expected normalized provider %q missing from candidates (got %d total)", providerID, len(candidates))
			continue
		}
		if path != providerExecutionPathNormalized {
			t.Errorf("provider %q: expected normalized path, got %q", providerID, path)
		}
	}

	// Extra sanity.
	for _, c := range candidates {
		if _, okTunnel := expectedTunnel[c.providerID]; !okTunnel {
			if _, okNorm := expectedNormalized[c.providerID]; !okNorm {
				t.Errorf("unexpected providerID %q", c.providerID)
			}
		}
	}
}

// TestResolveProviderPoolCandidatesKeepsMixedProviderTypes verifies S05.
func TestResolveProviderPoolCandidatesKeepsMixedProviderTypes(t *testing.T) {
	catalog := []config.ModelCatalogEntry{
		{
			ID: "mixed-model",
			Providers: map[string]string{
				"prov-vllm":     "served-vllm",
				"prov-vllm-mlx": "served-vllm-mlx",
				"prov-lemonade": "served-lemonade",
				"prov-ollama":   "served-ollama",
			},
		},
	}

	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"},
				{Name: "vllm-gpu-mlx", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
			},
			OllamaInstances: []config.OllamaInstanceConf{
				{Name: "ollama", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
			},
			OpenAICompatInstances: []config.OpenAICompatInstanceConf{
				{Name: "oaicompat", Enabled: true, Endpoint: "http://127.0.0.1:9000/v1"},
			},
		},
		Providers: []config.NodeProviderConf{
			{
				ID:       "prov-vllm",
				Type:     "vllm",
				Adapter:  "vllm-gpu",
				Models:   []string{"served-vllm"},
				Health:   "available",
				Capacity: 2,
			},
			{
				ID:       "prov-vllm-mlx",
				Type:     "vllm-mlx",
				Adapter:  "vllm-gpu-mlx",
				Models:   []string{"served-vllm-mlx"},
				Health:   "available",
				Capacity: 2,
			},
			{
				ID:       "prov-lemonade",
				Type:     "lemonade",
				Adapter:  "oaicompat",
				Models:   []string{"served-lemonade"},
				Health:   "available",
				Capacity: 2,
			},
			{
				ID:       "prov-ollama",
				Type:     "ollama",
				Adapter:  "ollama",
				Models:   []string{"served-ollama"},
				Health:   "available",
				Capacity: 2,
			},
		},
	})

	reg := edgenode.NewRegistry()
	for _, rec := range store.All() {
		reg.Register(&edgenode.NodeEntry{NodeID: rec.ID, LifecycleState: edgenode.LifecycleConnected})
	}

	svc := New(reg, nil)
	svc.SetNodeStore(store)
	svc.SetModelCatalog(catalog)

	req := SubmitRunRequest{ModelGroupKey: "mixed-model", ProviderPool: true}
	candidates, _, err := svc.resolveProviderPoolCandidates(req, store, catalog)
	if err != nil {
		t.Fatalf("resolveProviderPoolCandidates: %v", err)
	}

	// All 4 providers must be candidates.
	if len(candidates) != 4 {
		ids := make([]string, len(candidates))
		for i, c := range candidates {
			ids[i] = c.providerID
		}
		t.Fatalf("expected 4 candidates (prov-vllm, prov-vllm-mlx, prov-lemonade, prov-ollama), got %d: %v", len(candidates), ids)
	}

	byID := map[string]*candidateNode{}
	for i := range candidates {
		byID[candidates[i].providerID] = &candidates[i]
	}

	// OpenAI-compatible providers (vLLM, vLLM-MLX, lemonade) must be tunnel path.
	for _, provID := range []string{"prov-vllm", "prov-vllm-mlx", "prov-lemonade"} {
		c, ok := byID[provID]
		if !ok {
			t.Errorf("%q not in candidates", provID)
			continue
		}
		if c.executionPath != providerExecutionPathTunnel {
			t.Errorf("%q: expected tunnel path, got %q", provID, c.executionPath)
		}
		// Served target must match the catalog entry.
		expectedServed := catalog[0].Providers[provID]
		if c.servedTarget != expectedServed {
			t.Errorf("%q servedTarget: got %q, want %q", provID, c.servedTarget, expectedServed)
		}
	}

	// Ollama provider must be normalized path.
	ollama, ok := byID["prov-ollama"]
	if !ok {
		t.Fatal("prov-ollama not in candidates")
	}
	if ollama.executionPath != providerExecutionPathNormalized {
		t.Errorf("prov-ollama: expected normalized path, got %q", ollama.executionPath)
	}
	if ollama.servedTarget != "served-ollama" {
		t.Errorf("prov-ollama servedTarget: got %q, want %q", ollama.servedTarget, "served-ollama")
	}
}
