package config_test

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"iop/packages/go/config"
)

func TestLoadEdge_MultiOllamaInstances(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - alias: "multi"
    adapters:
      ollama_instances:
        - name: "local"
          enabled: true
          base_url: "http://127.0.0.1:11434"
          context_size: 131072
          capacity: 2
          max_queue: 4
          queue_timeout_ms: 1000
          request_timeout_ms: 20000
        - name: "dgx"
          enabled: true
          base_url: "http://192.168.0.91:11434"
          context_size: 262144
          capacity: 6
          max_queue: 12
          queue_timeout_ms: 2000
          request_timeout_ms: 60000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if len(cfg.Nodes) == 0 {
		t.Fatal("expected 1 node")
	}
	insts := cfg.Nodes[0].Adapters.OllamaInstances
	if len(insts) != 2 {
		t.Fatalf("expected 2 ollama instances, got %d", len(insts))
	}
	if insts[0].Name != "local" || insts[0].BaseURL != "http://127.0.0.1:11434" || insts[0].ContextSize != 131072 {
		t.Errorf("unexpected instance[0]: %+v", insts[0])
	}
	if insts[0].Capacity != 2 || insts[0].MaxQueue != 4 || insts[0].QueueTimeoutMS != 1000 || insts[0].RequestTimeoutMS != 20000 {
		t.Errorf("unexpected instance[0] queue config: %+v", insts[0])
	}
	if insts[1].Name != "dgx" || insts[1].BaseURL != "http://192.168.0.91:11434" || insts[1].ContextSize != 262144 {
		t.Errorf("unexpected instance[1]: %+v", insts[1])
	}
	if insts[1].Capacity != 6 || insts[1].MaxQueue != 12 || insts[1].QueueTimeoutMS != 2000 || insts[1].RequestTimeoutMS != 60000 {
		t.Errorf("unexpected instance[1] queue config: %+v", insts[1])
	}
}

func TestLoadEdge_MultiVllmInstances(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - alias: "multi-vllm"
    adapters:
      vllm_instances:
        - name: "a100"
          enabled: true
          endpoint: "http://10.0.0.5:8000"
          capacity: 4
          max_queue: 10
          queue_timeout_ms: 1500
          request_timeout_ms: 45000
        - name: "h100"
          enabled: true
          endpoint: "http://10.0.0.6:8000"
          capacity: 8
          max_queue: 16
          queue_timeout_ms: 2500
          request_timeout_ms: 90000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if len(cfg.Nodes) == 0 {
		t.Fatal("expected 1 node")
	}
	insts := cfg.Nodes[0].Adapters.VllmInstances
	if len(insts) != 2 {
		t.Fatalf("expected 2 vllm instances, got %d", len(insts))
	}
	if insts[0].Name != "a100" || insts[0].Endpoint != "http://10.0.0.5:8000" {
		t.Errorf("unexpected instance[0]: %+v", insts[0])
	}
	if insts[0].Capacity != 4 || insts[0].MaxQueue != 10 || insts[0].QueueTimeoutMS != 1500 || insts[0].RequestTimeoutMS != 45000 {
		t.Errorf("unexpected instance[0] queue config: %+v", insts[0])
	}
	if insts[1].Name != "h100" || insts[1].Endpoint != "http://10.0.0.6:8000" {
		t.Errorf("unexpected instance[1]: %+v", insts[1])
	}
	if insts[1].Capacity != 8 || insts[1].MaxQueue != 16 || insts[1].QueueTimeoutMS != 2500 || insts[1].RequestTimeoutMS != 90000 {
		t.Errorf("unexpected instance[1] queue config: %+v", insts[1])
	}
}

func TestLoadEdge_LegacyOllamaPromotedToInstances(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - alias: "local"
    adapters:
      ollama:
        enabled: true
        base_url: "http://192.168.0.91:11434"
        context_size: 262144
        capacity: 5
        max_queue: 9
        queue_timeout_ms: 1700
        request_timeout_ms: 55000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	insts := cfg.Nodes[0].Adapters.OllamaInstances
	if len(insts) != 1 {
		t.Fatalf("expected 1 ollama instance from legacy field, got %d", len(insts))
	}
	if insts[0].Name != "ollama" {
		t.Errorf("expected instance name %q, got %q", "ollama", insts[0].Name)
	}
	if insts[0].BaseURL != "http://192.168.0.91:11434" {
		t.Errorf("expected base_url %q, got %q", "http://192.168.0.91:11434", insts[0].BaseURL)
	}
	if insts[0].Capacity != 5 || insts[0].MaxQueue != 9 || insts[0].QueueTimeoutMS != 1700 || insts[0].RequestTimeoutMS != 55000 {
		t.Errorf("unexpected promoted queue config: %+v", insts[0])
	}
}

func TestLoadEdge_DuplicateOllamaInstanceNameRejected(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - alias: "dup"
    adapters:
      ollama_instances:
        - name: "same"
          enabled: true
          base_url: "http://127.0.0.1:11434"
        - name: "same"
          enabled: true
          base_url: "http://127.0.0.2:11434"
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected error for duplicate ollama instance name")
	}
}

func TestLoadEdge_ProviderQueueConfigRejectsNegative(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - alias: "bad-queue"
    adapters:
      ollama_instances:
        - name: "local"
          enabled: true
          base_url: "http://127.0.0.1:11434"
          capacity: -1
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected negative provider queue config error")
	}
	if !strings.Contains(err.Error(), "capacity") || !strings.Contains(err.Error(), "non-negative") {
		t.Fatalf("expected error to mention capacity non-negative, got %v", err)
	}
}

func TestNormalizeAdapters_OllamaIdempotent(t *testing.T) {
	a := config.AdaptersConf{
		Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434", ContextSize: 4096},
	}
	if err := config.NormalizeAdapters(&a); err != nil {
		t.Fatalf("first normalize: %v", err)
	}
	if len(a.OllamaInstances) != 1 {
		t.Fatalf("expected 1 instance after first normalize, got %d", len(a.OllamaInstances))
	}
	if err := config.NormalizeAdapters(&a); err != nil {
		t.Fatalf("second normalize (idempotent): %v", err)
	}
	if len(a.OllamaInstances) != 1 {
		t.Fatalf("expected still 1 instance after second normalize, got %d", len(a.OllamaInstances))
	}
}

func TestNormalizeAdapters_VllmIdempotent(t *testing.T) {
	a := config.AdaptersConf{
		Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"},
	}
	if err := config.NormalizeAdapters(&a); err != nil {
		t.Fatalf("first normalize: %v", err)
	}
	if len(a.VllmInstances) != 1 {
		t.Fatalf("expected 1 instance after first normalize, got %d", len(a.VllmInstances))
	}
	if err := config.NormalizeAdapters(&a); err != nil {
		t.Fatalf("second normalize (idempotent): %v", err)
	}
	if len(a.VllmInstances) != 1 {
		t.Fatalf("expected still 1 instance after second normalize, got %d", len(a.VllmInstances))
	}
}

func TestNormalizeAdapters_OllamaConflictErrors(t *testing.T) {
	a := config.AdaptersConf{
		Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"},
		OllamaInstances: []config.OllamaInstanceConf{
			{Name: "ollama", Enabled: true, BaseURL: "http://other-host:11434"},
		},
	}
	if err := config.NormalizeAdapters(&a); err == nil {
		t.Fatal("expected error for conflicting legacy ollama and explicit instance with same name")
	}
}

func TestNormalizeAdapters_VllmConflictErrors(t *testing.T) {
	a := config.AdaptersConf{
		Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"},
		VllmInstances: []config.VllmInstanceConf{
			{Name: "vllm", Enabled: true, Endpoint: "http://other-host:8000"},
		},
	}
	if err := config.NormalizeAdapters(&a); err == nil {
		t.Fatal("expected error for conflicting legacy vllm and explicit instance with same name")
	}
}

func TestNormalizeAdapters_OllamaDisabledSameNameErrors(t *testing.T) {
	a := config.AdaptersConf{
		Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://127.0.0.1:11434"},
		OllamaInstances: []config.OllamaInstanceConf{
			{Name: "ollama", Enabled: false, BaseURL: "http://127.0.0.1:11434"},
		},
	}
	if err := config.NormalizeAdapters(&a); err == nil {
		t.Fatal("expected error for legacy enabled ollama conflicting with same-name disabled explicit instance")
	}
}

func TestNormalizeAdapters_VllmDisabledSameNameErrors(t *testing.T) {
	a := config.AdaptersConf{
		Vllm: config.VllmConf{Enabled: true, Endpoint: "http://127.0.0.1:8000"},
		VllmInstances: []config.VllmInstanceConf{
			{Name: "vllm", Enabled: false, Endpoint: "http://127.0.0.1:8000"},
		},
	}
	if err := config.NormalizeAdapters(&a); err == nil {
		t.Fatal("expected error for legacy enabled vllm conflicting with same-name disabled explicit instance")
	}
}

func TestLoadEdge_OllamaContextSize(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - alias: "local"
    adapters:
      ollama:
        enabled: true
        base_url: "http://192.168.0.91:11434"
        context_size: 262144
        capacity: 3
        max_queue: 8
        queue_timeout_ms: 1500
        request_timeout_ms: 30000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}

	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	ollama := cfg.Nodes[0].Adapters.Ollama
	if !ollama.Enabled {
		t.Fatal("expected ollama.enabled=true")
	}
	if ollama.BaseURL != "http://192.168.0.91:11434" || ollama.ContextSize != 262144 {
		t.Fatalf("unexpected ollama config: %+v", ollama)
	}
	if ollama.Capacity != 3 || ollama.MaxQueue != 8 || ollama.QueueTimeoutMS != 1500 || ollama.RequestTimeoutMS != 30000 {
		t.Fatalf("unexpected ollama queue config: %+v", ollama)
	}
}

// Provider pool schema validation tests (G06)

func TestLoadEdge_ModelCatalogBasicHappyPath(t *testing.T) {
	// Happy path: models[].providers values must match nodes[].providers[].models.
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "qwen3.6:35b"
    display_name: "Qwen 3.6 35B"
    context_window_tokens: 262144
    default_max_tokens: 32768
    min_max_tokens: 32768
    default_thinking_token_budget: 8192
    providers:
      vllm-gpu: "nvidia/Qwen3.6-35B-A3B-NVFP4"
      ollama-local: "qwen3.6:35b"
nodes:
  - id: "node-gpu-01"
    alias: "gpu-node"
    providers:
      - id: "vllm-gpu"
        type: "vllm"
        category: "api"
        models:
          - "nvidia/Qwen3.6-35B-A3B-NVFP4"
        capacity: 4
      - id: "ollama-local"
        type: "ollama"
        category: "local_inference"
        models:
          - "qwen3.6:35b"
        capacity: 2
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if len(cfg.Models) != 1 {
		t.Fatalf("expected 1 model catalog entry, got %d", len(cfg.Models))
	}
	m := cfg.Models[0]
	if m.ID != "qwen3.6:35b" {
		t.Errorf("models[0].ID = %q, want %q", m.ID, "qwen3.6:35b")
	}
	if m.DisplayName != "Qwen 3.6 35B" {
		t.Errorf("models[0].DisplayName = %q, want %q", m.DisplayName, "Qwen 3.6 35B")
	}
	if m.ContextWindowTokens != 262144 {
		t.Errorf("models[0].ContextWindowTokens = %d, want 262144", m.ContextWindowTokens)
	}
	if m.DefaultMaxTokens != 32768 {
		t.Errorf("models[0].DefaultMaxTokens = %d, want 32768", m.DefaultMaxTokens)
	}
	if m.MinMaxTokens != 32768 {
		t.Errorf("models[0].MinMaxTokens = %d, want 32768", m.MinMaxTokens)
	}
	if m.DefaultThinkingTokenBudget != 8192 {
		t.Errorf("models[0].DefaultThinkingTokenBudget = %d, want 8192", m.DefaultThinkingTokenBudget)
	}
	if len(m.Providers) != 2 {
		t.Fatalf("expected 2 providers, got %d", len(m.Providers))
	}
	if m.Providers["vllm-gpu"] != "nvidia/Qwen3.6-35B-A3B-NVFP4" {
		t.Errorf("providers[vllm-gpu] = %q, want %q", m.Providers["vllm-gpu"], "nvidia/Qwen3.6-35B-A3B-NVFP4")
	}
	if m.Providers["ollama-local"] != "qwen3.6:35b" {
		t.Errorf("providers[ollama-local] = %q, want %q", m.Providers["ollama-local"], "qwen3.6:35b")
	}
	if len(cfg.Nodes) != 1 {
		t.Fatalf("expected 1 node, got %d", len(cfg.Nodes))
	}
	if len(cfg.Nodes[0].Providers) != 2 {
		t.Fatalf("expected 2 node providers, got %d", len(cfg.Nodes[0].Providers))
	}
	// Check category resolution.
	for _, p := range cfg.Nodes[0].Providers {
		if p.ID == "vllm-gpu" && p.Category != config.CategoryAPI {
			t.Errorf("providers[vllm-gpu].Category = %q, want %q", p.Category, config.CategoryAPI)
		}
		if p.ID == "ollama-local" && p.Category != config.CategoryLocalInference {
			t.Errorf("providers[ollama-local].Category = %q, want %q", p.Category, config.CategoryLocalInference)
		}
	}
}

func TestLoadEdge_ConfigExampleMixedAndOllamaOnlyProviderGroups(t *testing.T) {
	f := filepath.Join("..", "..", "..", "configs", "edge.yaml")
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load example config: %v", err)
	}

	models := map[string]config.ModelCatalogEntry{}
	for _, m := range cfg.Models {
		models[m.ID] = m
	}
	mixed, ok := models["qwen3.6:35b"]
	if !ok {
		t.Fatal("expected qwen3.6:35b mixed model group in example config")
	}
	if mixed.Providers["mac-mlx-vllm"] != "mlx-community/Qwen3.6-35B-A3B-4bit" {
		t.Fatalf("expected mac-mlx-vllm served model in mixed group, got %+v", mixed.Providers)
	}
	if mixed.Providers["ollama-local"] != "qwen3.6:35b" {
		t.Fatalf("expected ollama-local fallback in mixed group, got %+v", mixed.Providers)
	}
	ollamaOnly, ok := models["local-llama3"]
	if !ok {
		t.Fatal("expected local-llama3 Ollama-only model group in example config")
	}
	if len(ollamaOnly.Providers) != 1 || ollamaOnly.Providers["ollama-local"] != "llama3.1:8b" {
		t.Fatalf("expected local-llama3 to route only to ollama-local, got %+v", ollamaOnly.Providers)
	}

	providers := map[string]config.NodeProviderConf{}
	for _, n := range cfg.Nodes {
		for _, p := range n.Providers {
			providers[p.ID] = p
		}
	}
	vllm := providers["mac-mlx-vllm"]
	ollama := providers["ollama-local"]
	if vllm.Type == "" || ollama.Type == "" {
		t.Fatalf("expected both mac-mlx-vllm and ollama-local providers, got %+v", providers)
	}
	if config.NormalizeProviderType(vllm.Type) != "openai_compat" {
		t.Fatalf("mac-mlx-vllm should classify as OpenAI-compatible, got %q", vllm.Type)
	}
	if config.NormalizeProviderType(ollama.Type) != "ollama" {
		t.Fatalf("ollama-local should classify as normalized Ollama, got %q", ollama.Type)
	}
	if ollama.Capacity != 1 || ollama.Priority <= vllm.Priority {
		t.Fatalf("expected Ollama example to use lower capacity and higher priority number than vLLM, got vllm=%+v ollama=%+v", vllm, ollama)
	}
}

func TestLoadEdge_SeulgivibeProviderFirstAliases(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
openai:
  provider_auth:
    enabled: true
models:
  - id: "claude-sonnet-4-5"
    providers:
      seulgivibe-claude: "claude-sonnet-4-5"
  - id: "claude-opus-4-8"
    providers:
      seulgivibe-claude: "claude-opus-4-8"
  - id: "claude-fable-5"
    providers:
      seulgivibe-claude: "claude-fable-5"
  - id: "gpt-5.1"
    providers:
      seulgivibe-openai: "gpt-5.1"
  - id: "gpt-5.5"
    providers:
      seulgivibe-openai: "gpt-5.5"
nodes:
  - id: "node-seulgivibe"
    alias: "seulgivibe-node"
    providers:
      - id: "seulgivibe-claude"
        type: "seulgivibe_claude"
        category: "api"
        endpoint: "https://seulgivibe.example.invalid/anthropic/v1"
        models:
          - "claude-sonnet-4-5"
          - "claude-opus-4-8"
          - "claude-fable-5"
        capacity: 4
      - id: "seulgivibe-openai"
        type: "seulgivibe_openai"
        category: "api"
        endpoint: "https://seulgivibe.example.invalid/openai/v1"
        models:
          - "gpt-5.1"
          - "gpt-5.5"
        capacity: 4
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if len(cfg.Models) != 5 {
		t.Fatalf("expected 5 Seulgivibe model catalog entries, got %d", len(cfg.Models))
	}
	if got := config.NormalizeProviderType(cfg.Nodes[0].Providers[0].Type); got != "openai_compat" {
		t.Fatalf("seulgivibe_claude should normalize to openai_compat, got %q", got)
	}
	if got := config.NormalizeProviderType(cfg.Nodes[0].Providers[1].Type); got != "openai_compat" {
		t.Fatalf("seulgivibe_openai should normalize to openai_compat, got %q", got)
	}
	auth := cfg.OpenAI.ProviderAuth
	if !auth.Enabled || auth.FromHeader != "X-IOP-Provider-Authorization" || auth.TargetHeader != "Authorization" || auth.Scheme != "Bearer" || !auth.Required {
		t.Fatalf("unexpected provider_auth defaults: %+v", auth)
	}
	for _, m := range cfg.Models {
		for providerID, servedModel := range m.Providers {
			if strings.TrimSpace(servedModel) == "" {
				t.Fatalf("model %q provider %q has empty served model", m.ID, providerID)
			}
		}
	}
}

func TestLoadEdge_ModelCatalogEmptyModelIDRejected(t *testing.T) {
	// No models[] on node, so serve model check is skipped (legacy compat).
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: ""
    providers:
      vllm-gpu: "model-a"
nodes:
  - id: "node-01"
    providers:
      - id: "vllm-gpu"
        type: "vllm"
        category: "api"
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected error for empty model id")
	}
	if !strings.Contains(err.Error(), "models[0]") || !strings.Contains(err.Error(), "id must not be empty") {
		t.Fatalf("expected error mentioning models[0] and id must not be empty, got %v", err)
	}
}

func TestLoadEdge_ModelCatalogDuplicateModelIDRejected(t *testing.T) {
	// serve model membership fail → 다른 error로 먼저 실패해야 하지만 duplicate 체크가
	// 먼저 발생하므로 models validation 전에 seen check가 먼저 통과하는 fixture.
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "qwen3.6:35b"
    providers:
      vllm-gpu: "model-a"
  - id: "qwen3.6:35b"
    providers:
      ollama: "qwen3.6"
nodes:
  - id: "node-01"
    providers:
      - id: "vllm-gpu"
        type: "vllm"
        category: "api"
        models:
          - "model-a"
      - id: "ollama"
        type: "ollama"
        category: "local_inference"
        models:
          - "qwen3.6"
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected error for duplicate model id")
	}
	if !strings.Contains(err.Error(), "duplicate model id") {
		t.Fatalf("expected error mentioning duplicate model id, got %v", err)
	}
}

func TestLoadEdge_ModelCatalogEmptyProvidersRejected(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "qwen3.6:35b"
    providers: {}
nodes:
  - id: "node-01"
    providers:
      - id: "vllm-gpu"
        type: "vllm"
        category: "api"
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected error for empty providers")
	}
	if !strings.Contains(err.Error(), "providers must not be empty") {
		t.Fatalf("expected error mentioning providers must not be empty, got %v", err)
	}
}

func TestLoadEdge_ModelCatalogContextWindowRejectsNegative(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "qwen3.6:35b"
    context_window_tokens: -1
    providers:
      vllm-gpu: "model-a"
nodes:
  - id: "node-01"
    providers:
      - id: "vllm-gpu"
        type: "vllm"
        category: "api"
        models:
          - "model-a"
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected error for negative context_window_tokens")
	}
	if !strings.Contains(err.Error(), "context_window_tokens must be non-negative") {
		t.Fatalf("expected error mentioning context_window_tokens, got %v", err)
	}
}

func TestLoadEdge_ModelCatalogServedModelMembershipMissing(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "qwen3.6:35b"
    providers:
      vllm-gpu: "typo-model"
nodes:
  - id: "node-gpu-01"
    providers:
      - id: "vllm-gpu"
        type: "vllm"
        category: "api"
        models:
          - "nvidia/Qwen3.6-35B-A3B-NVFP4"
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected error for served model not in provider's models list")
	}
	if !strings.Contains(err.Error(), "typo-model") || !strings.Contains(err.Error(), "vllm-gpu") {
		t.Fatalf("expected error mentioning typo-model and vllm-gpu, got %v", err)
	}
}

func TestLoadEdge_ModelCatalogUnresolvedProviderRejected(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "qwen3.6:35b"
    providers:
      non-existent: "model-a"
nodes:
  - id: "node-01"
    providers:
      - id: "vllm-gpu"
        type: "vllm"
        category: "api"
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected error for unresolved provider id")
	}
	if !strings.Contains(err.Error(), "does not resolve") || !strings.Contains(err.Error(), "non-existent") {
		t.Fatalf("expected error mentioning does not resolve and non-existent, got %v", err)
	}
}

// ---------- PROVIDER_POLICY_CONFIG: root provider_pool queue policy tests ----------

// TestLoadEdge_ProviderPoolPolicyCanonical verifies S06: root provider_pool
// is the canonical queue policy owner and its explicit values are honored.
func TestLoadEdge_ProviderPoolPolicyCanonical(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
provider_pool:
  max_queue: 32
  queue_timeout_ms: 60000
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if cfg.ProviderPool.MaxQueue != 32 {
		t.Errorf("expected canonical max_queue=32, got %d", cfg.ProviderPool.MaxQueue)
	}
	if cfg.ProviderPool.QueueTimeoutMS != 60000 {
		t.Errorf("expected canonical queue_timeout_ms=60000, got %d", cfg.ProviderPool.QueueTimeoutMS)
	}
}

// TestLoadEdge_ProviderPoolPolicyCanonicalMaxZeroDefaults verifies that
// canonical max_queue=0 normalizes to the default 16, preserving other values.
func TestLoadEdge_ProviderPoolPolicyCanonicalMaxZeroDefaults(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
provider_pool:
  max_queue: 0
  queue_timeout_ms: 45000
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if cfg.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue {
		t.Errorf("expected normalized max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue)
	}
	if cfg.ProviderPool.QueueTimeoutMS != 45000 {
		t.Errorf("expected queue_timeout_ms=45000, got %d", cfg.ProviderPool.QueueTimeoutMS)
	}
}

// TestLoadEdge_ProviderPoolPolicyCanonicalExplicitZeroTimeout verifies that
// explicit queue_timeout_ms=0 is preserved as no-timeout.
func TestLoadEdge_ProviderPoolPolicyCanonicalExplicitZeroTimeout(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
provider_pool:
  max_queue: 20
  queue_timeout_ms: 0
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if cfg.ProviderPool.MaxQueue != 20 {
		t.Errorf("expected max_queue=20, got %d", cfg.ProviderPool.MaxQueue)
	}
	if cfg.ProviderPool.QueueTimeoutMS != 0 {
		t.Errorf("expected explicit zero timeout preserved, got %d", cfg.ProviderPool.QueueTimeoutMS)
	}
}

// TestLoadEdge_ProviderPoolPolicyLegacyIdenticalPromotion verifies that when
// no canonical is set, identical legacy pairs across providers are promoted.
func TestLoadEdge_ProviderPoolPolicyLegacyIdenticalPromotion(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
        max_queue: 8
        queue_timeout_ms: 15000
      - id: "prov-b"
        type: "ollama"
        category: "local_inference"
        models: ["model-b"]
        capacity: 3
        max_queue: 8
        queue_timeout_ms: 15000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if cfg.ProviderPool.MaxQueue != 8 {
		t.Errorf("expected promoted max_queue=8, got %d", cfg.ProviderPool.MaxQueue)
	}
	if cfg.ProviderPool.QueueTimeoutMS != 15000 {
		t.Errorf("expected promoted queue_timeout_ms=15000, got %d", cfg.ProviderPool.QueueTimeoutMS)
	}
}

// TestLoadEdge_ProviderPoolPolicyLegacyConflict verifies that differing
// legacy pairs across providers produce a deterministic error naming both
// providers.
func TestLoadEdge_ProviderPoolPolicyLegacyConflict(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
        max_queue: 8
        queue_timeout_ms: 15000
      - id: "prov-b"
        type: "ollama"
        category: "local_inference"
        models: ["model-b"]
        capacity: 3
        max_queue: 16
        queue_timeout_ms: 15000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected conflict error for differing legacy queue policy")
	}
	if !strings.Contains(err.Error(), "conflicting provider queue policy") {
		t.Fatalf("expected conflict error, got: %v", err)
	}
	if !strings.Contains(err.Error(), "prov-a") || !strings.Contains(err.Error(), "prov-b") {
		t.Fatalf("expected error to name both conflicting providers, got: %v", err)
	}
}

// TestLoadEdge_ProviderPoolPolicyOrderIndependentConflict verifies that the
// conflict detection is order-independent (swapping prov-a and prov-b order).
func TestLoadEdge_ProviderPoolPolicyOrderIndependentConflict(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-01"
    providers:
      - id: "prov-b"
        type: "ollama"
        category: "local_inference"
        models: ["model-b"]
        capacity: 3
        max_queue: 16
        queue_timeout_ms: 15000
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
        max_queue: 8
        queue_timeout_ms: 15000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	_, err := config.LoadEdge(f)
	if err == nil {
		t.Fatal("expected conflict error for reversed legacy queue policy order")
	}
	if !strings.Contains(err.Error(), "conflicting provider queue policy") {
		t.Fatalf("expected conflict error regardless of provider order, got: %v", err)
	}
}

// TestLoadEdge_ProviderPoolPolicyDefaults verifies that with no canonical and
// no usable legacy values, defaults are used.
func TestLoadEdge_ProviderPoolPolicyDefaults(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if cfg.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue {
		t.Errorf("expected default max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue)
	}
	if cfg.ProviderPool.QueueTimeoutMS != config.DefaultProviderPoolQueueTimeoutMS {
		t.Errorf("expected default queue_timeout_ms=%d, got %d", config.DefaultProviderPoolQueueTimeoutMS, cfg.ProviderPool.QueueTimeoutMS)
	}
}

// TestLoadEdge_ProviderPoolPolicyCanonicalOverridesLegacy verifies that when
// canonical is set, legacy per-provider values do not affect the effective policy.
func TestLoadEdge_ProviderPoolPolicyCanonicalOverridesLegacy(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
provider_pool:
  max_queue: 10
  queue_timeout_ms: 5000
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
        max_queue: 99
        queue_timeout_ms: 99000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if cfg.ProviderPool.MaxQueue != 10 {
		t.Errorf("expected canonical max_queue=10 to override legacy, got %d", cfg.ProviderPool.MaxQueue)
	}
	if cfg.ProviderPool.QueueTimeoutMS != 5000 {
		t.Errorf("expected canonical queue_timeout_ms=5000 to override legacy, got %d", cfg.ProviderPool.QueueTimeoutMS)
	}
}

// ---------- PROVIDER_POLICY_CONFIG: canonical negative rejection (G04 followup) ----------

// TestLoadEdge_ProviderPoolPolicyCanonicalNegativeRejected verifies S06 that
// negative max_queue and queue_timeout_ms are rejected before default
// normalization can mask them.
func TestLoadEdge_ProviderPoolPolicyCanonicalNegativeRejected(t *testing.T) {
	t.Run("max_queue negative", func(t *testing.T) {
		dir := t.TempDir()
		f := filepath.Join(dir, "edge.yaml")
		yaml := `
server:
  listen: "0.0.0.0:9090"
provider_pool:
  max_queue: -1
  queue_timeout_ms: 30000
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for negative max_queue")
		}
		if !strings.Contains(err.Error(), "provider_pool.max_queue") || !strings.Contains(err.Error(), "non-negative") {
			t.Fatalf("expected error to mention provider_pool.max_queue non-negative, got: %v", err)
		}
	})

	t.Run("queue_timeout_ms negative", func(t *testing.T) {
		dir := t.TempDir()
		f := filepath.Join(dir, "edge.yaml")
		yaml := `
server:
  listen: "0.0.0.0:9090"
provider_pool:
  max_queue: 10
  queue_timeout_ms: -500
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for negative queue_timeout_ms")
		}
		if !strings.Contains(err.Error(), "provider_pool.queue_timeout_ms") || !strings.Contains(err.Error(), "non-negative") {
			t.Fatalf("expected error to mention provider_pool.queue_timeout_ms non-negative, got: %v", err)
		}
	})
}

// ---------- PROVIDER_POLICY_CONFIG: legacy partial fields and effective equality (G04 followup) ----------

// TestLoadEdge_ProviderPoolPolicyLegacyPartialFields verifies that legacy
// timeout-only and max-only pairs are normalized to effective (DefaultProviderPoolMaxQueue,
// timeout) and (max, 0) respectively before promotion.
func TestLoadEdge_ProviderPoolPolicyLegacyPartialFields(t *testing.T) {
	t.Run("timeout-only promotes to default-max", func(t *testing.T) {
		dir := t.TempDir()
		f := filepath.Join(dir, "edge.yaml")
		yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
        queue_timeout_ms: 20000
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		if cfg.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue {
			t.Errorf("expected normalized max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue)
		}
		if cfg.ProviderPool.QueueTimeoutMS != 20000 {
			t.Errorf("expected queue_timeout_ms=20000, got %d", cfg.ProviderPool.QueueTimeoutMS)
		}
	})

	t.Run("max-only preserves no-timeout", func(t *testing.T) {
		dir := t.TempDir()
		f := filepath.Join(dir, "edge.yaml")
		yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
        max_queue: 24
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		if cfg.ProviderPool.MaxQueue != 24 {
			t.Errorf("expected max_queue=24, got %d", cfg.ProviderPool.MaxQueue)
		}
		if cfg.ProviderPool.QueueTimeoutMS != 0 {
			t.Errorf("expected no-timeout preserved (0), got %d", cfg.ProviderPool.QueueTimeoutMS)
		}
	})
}

// TestLoadEdge_ProviderPoolPolicyLegacyEffectiveEquality verifies that a
// timeout-only provider (max_queue=0) and a provider with max_queue=Default
// are treated as equal effective pairs and promoted without conflict.
func TestLoadEdge_ProviderPoolPolicyLegacyEffectiveEquality(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")
	yaml := `
server:
  listen: "0.0.0.0:9090"
nodes:
  - id: "node-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
        queue_timeout_ms: 20000
      - id: "prov-b"
        type: "ollama"
        category: "local_inference"
        models: ["model-b"]
        capacity: 3
        max_queue: 16
        queue_timeout_ms: 20000
`
	if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write yaml: %v", err)
	}
	cfg, err := config.LoadEdge(f)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	// Both normalize to (DefaultProviderPoolMaxQueue, 20000).
	if cfg.ProviderPool.MaxQueue != config.DefaultProviderPoolMaxQueue {
		t.Errorf("expected normalized max_queue=%d, got %d", config.DefaultProviderPoolMaxQueue, cfg.ProviderPool.MaxQueue)
	}
	if cfg.ProviderPool.QueueTimeoutMS != 20000 {
		t.Errorf("expected queue_timeout_ms=20000, got %d", cfg.ProviderPool.QueueTimeoutMS)
	}
}
