package config_test

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

	"iop/packages/go/config"
)

// --- S01: Overlay resolution, rejection, and legacy alias normalization ---

func TestProtocolProfileOverlayResolution(t *testing.T) {
	catalog := map[string]config.ProtocolProfileConf{
		"base": {
			Driver:  config.ProtocolDriverOpenAIChat,
			BaseURL: "https://api.example.com/v1",
			Operations: map[string]string{
				"models": "/models",
			},
			Auth:         config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
			Capabilities: []string{"models", "chat"},
		},
		"overlay": {
			Base: "base",
			Operations: map[string]string{
				"chat_completions": "/chat/completions",
			},
			Capabilities: []string{"models", "chat", "streaming"},
		},
	}

	resolved, err := config.ResolveProtocolProfile("overlay", "", catalog)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}
	if resolved.ID != "overlay" {
		t.Errorf("ID = %q, want %q", resolved.ID, "overlay")
	}
	if resolved.Driver != config.ProtocolDriverOpenAIChat {
		t.Errorf("Driver = %q, want %q", resolved.Driver, config.ProtocolDriverOpenAIChat)
	}
	if resolved.BaseURL != "https://api.example.com/v1" {
		t.Errorf("BaseURL = %q, want inherited value", resolved.BaseURL)
	}
	if _, ok := resolved.Operations["models"]; !ok {
		t.Errorf("expected inherited operation models")
	}
	if _, ok := resolved.Operations["chat_completions"]; !ok {
		t.Errorf("expected overlay operation chat_completions")
	}
	if len(resolved.Capabilities) != 3 {
		t.Errorf("Capabilities = %v, want 3", resolved.Capabilities)
	}
}

func TestProtocolProfileOverlayDeepCopyImmutability(t *testing.T) {
	baseOps := map[string]string{"models": "/models"}
	catalog := map[string]config.ProtocolProfileConf{
		"base": {
			Driver:       config.ProtocolDriverOpenAIChat,
			BaseURL:      "https://api.example.com/v1",
			Operations:   baseOps,
			Auth:         config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
			Capabilities: []string{"models", "chat"},
		},
		"overlay": {
			Base:       "base",
			Operations: map[string]string{"chat_completions": "/chat/completions"},
		},
	}

	resolved, err := config.ResolveProtocolProfile("overlay", "", catalog)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}

	// Mutate the resolved snapshot and verify the catalog is unaffected.
	resolved.Operations["injected"] = "/injected"
	if _, ok := catalog["base"].Operations["injected"]; ok {
		t.Error("mutating resolved profile affected catalog base")
	}
	if _, ok := catalog["overlay"].Operations["injected"]; ok {
		t.Error("mutating resolved profile affected catalog overlay")
	}
}

// TestGeminiProtocolProfileReasoningEffortScale asserts that the built-in
// Gemini Chat profile exposes only the portable low/medium/high effort grades
// and that the common nearest-lower normalizer demotes xhigh and max to high
// while rejecting none. It also verifies the operation-scoped facts survive
// base: gemini inheritance.
func TestGeminiProtocolProfileReasoningEffortScale(t *testing.T) {
	profile, err := config.ResolveProtocolProfile("gemini", "", config.BuiltInProtocolProfileCatalog())
	if err != nil {
		t.Fatalf("ResolveProtocolProfile(gemini): %v", err)
	}

	t.Run("portable grades map exactly", func(t *testing.T) {
		for _, level := range []string{"low", "medium", "high"} {
			got, ok := profile.MapReasoningEffort(config.OperationChatCompletions, level, true)
			if !ok || got != level {
				t.Fatalf("exact %s with tools = %q,%t; want %q,true", level, got, ok, level)
			}
		}
	})

	t.Run("xhigh falls back to high", func(t *testing.T) {
		got, ok := profile.MapReasoningEffort(config.OperationChatCompletions, "xhigh", true)
		if !ok || got != "high" {
			t.Fatalf("xhigh with tools = %q,%t; want high,true", got, ok)
		}
	})

	t.Run("max falls back to high", func(t *testing.T) {
		got, ok := profile.MapReasoningEffort(config.OperationChatCompletions, "max", true)
		if !ok || got != "high" {
			t.Fatalf("max with tools = %q,%t; want high,true", got, ok)
		}
	})

	t.Run("none is unsupported", func(t *testing.T) {
		_, ok := profile.MapReasoningEffort(config.OperationChatCompletions, "none", true)
		if ok {
			t.Fatal("none must be rejected; got ok=true")
		}
	})

	t.Run("with-tools true", func(t *testing.T) {
		mapping, ok := profile.EffortMapping(config.OperationChatCompletions)
		if !ok {
			t.Fatal("expected effort mapping for chat_completions")
		}
		if !mapping.WithTools {
			t.Error("expected WithTools=true")
		}
		if mapping.Wire != config.ProtocolEffortWireGeminiChat {
			t.Errorf("Wire = %q, want %q", mapping.Wire, config.ProtocolEffortWireGeminiChat)
		}
		if !mapping.TokenBudget {
			t.Error("expected TokenBudget=true")
		}
	})

	t.Run("base: gemini inherits the scale", func(t *testing.T) {
		catalog := config.BuiltInProtocolProfileCatalog()
		catalog["overlay-gemini"] = config.ProtocolProfileConf{Base: "gemini"}
		overlay, err := config.ResolveProtocolProfile("overlay-gemini", "", catalog)
		if err != nil {
			t.Fatalf("ResolveProtocolProfile(overlay-gemini): %v", err)
		}
		for _, level := range []string{"low", "medium", "high"} {
			got, ok := overlay.MapReasoningEffort(config.OperationChatCompletions, level, true)
			if !ok || got != level {
				t.Fatalf("overlay exact %s = %q,%t; want %q,true", level, got, ok, level)
			}
		}
		if got, _ := overlay.MapReasoningEffort(config.OperationChatCompletions, "xhigh", true); got != "high" {
			t.Errorf("overlay xhigh = %q, want high", got)
		}
		if _, ok := overlay.MapReasoningEffort(config.OperationChatCompletions, "none", true); ok {
			t.Error("overlay must reject none")
		}
	})
}

func TestProtocolProfileReasoningEffortNormalization(t *testing.T) {
	profile, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog())
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}
	if got, ok := profile.MapReasoningEffort(config.OperationResponses, "xhigh", true); !ok || got != "xhigh" {
		t.Fatalf("Responses xhigh with tools = %q,%t; want xhigh,true", got, ok)
	}
	if got, ok := profile.MapReasoningEffort(config.OperationChatCompletions, "high", true); ok || got != "" {
		t.Fatalf("Chat high with tools = %q,%t; want unsupported", got, ok)
	}
	if got, ok := profile.MapReasoningEffort(config.OperationChatCompletions, "high", false); !ok || got != "high" {
		t.Fatalf("Chat high without tools = %q,%t; want high,true", got, ok)
	}
}

func TestProtocolProfileEffortNormalizationOverlayAndValidation(t *testing.T) {
	catalog := config.BuiltInProtocolProfileCatalog()
	catalog["four-grade-openai"] = config.ProtocolProfileConf{
		Base: "openai",
		Normalization: config.ProtocolNormalizationConf{Effort: map[string]config.ProtocolEffortMappingConf{
			string(config.OperationResponses): {
				WithTools: true,
				Wire:      config.ProtocolEffortWireOpenAIResponses,
				Levels: map[string]string{
					"none": "none", "low": "low", "medium": "medium",
					"high": "high", "xhigh": "xhigh",
				},
			},
		}},
	}
	profile, err := config.ResolveProtocolProfile("four-grade-openai", "", catalog)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}
	if got, ok := profile.MapReasoningEffort(config.OperationResponses, "max", true); !ok || got != "xhigh" {
		t.Fatalf("mapped max = %q,%t; want xhigh,true", got, ok)
	}

	bad := config.BuiltInProtocolProfileCatalog()
	bad["bad-effort"] = config.ProtocolProfileConf{
		Base: "openai",
		Normalization: config.ProtocolNormalizationConf{Effort: map[string]config.ProtocolEffortMappingConf{
			"messages": {Wire: config.ProtocolEffortWireAnthropicMessage, Levels: map[string]string{"high": "high"}},
		}},
	}
	if _, err := config.ResolveProtocolProfile("bad-effort", "", bad); err == nil || !strings.Contains(err.Error(), "not declared") {
		t.Fatalf("expected undeclared effort operation error, got %v", err)
	}

	badUpgrade := config.BuiltInProtocolProfileCatalog()
	badUpgrade["bad-upgrade"] = config.ProtocolProfileConf{
		Base: "openai",
		Normalization: config.ProtocolNormalizationConf{Effort: map[string]config.ProtocolEffortMappingConf{
			string(config.OperationResponses): {
				WithTools: true,
				Wire:      config.ProtocolEffortWireOpenAIResponses,
				Levels:    map[string]string{"high": "max"},
			},
		}},
	}
	if _, err := config.ResolveProtocolProfile("bad-upgrade", "", badUpgrade); err == nil || !strings.Contains(err.Error(), "upgrades") {
		t.Fatalf("expected effort upgrade error, got %v", err)
	}
}

func TestProtocolProfileToolCallNormalization(t *testing.T) {
	gemini, err := config.ResolveProtocolProfile("gemini", "", config.BuiltInProtocolProfileCatalog())
	if err != nil {
		t.Fatalf("ResolveProtocolProfile(gemini): %v", err)
	}
	mapping, ok := gemini.ToolCallMapping(config.OperationChatCompletions)
	if !ok || mapping.Wire != config.ProtocolToolCallWireGeminiChat {
		t.Fatalf("Gemini Chat tool-call mapping = %+v,%t", mapping, ok)
	}
	openAI, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfileCatalog())
	if err != nil {
		t.Fatalf("ResolveProtocolProfile(openai): %v", err)
	}
	if _, ok := openAI.ToolCallMapping(config.OperationChatCompletions); ok {
		t.Fatal("OpenAI profile must not inherit Gemini tool-call normalization")
	}

	catalog := config.BuiltInProtocolProfileCatalog()
	catalog["custom-gemini"] = config.ProtocolProfileConf{Base: "gemini", BaseURL: "https://proxy.example.invalid/v1"}
	custom, err := config.ResolveProtocolProfile("custom-gemini", "", catalog)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile(custom-gemini): %v", err)
	}
	if mapping, ok := custom.ToolCallMapping(config.OperationChatCompletions); !ok || mapping.Wire != config.ProtocolToolCallWireGeminiChat {
		t.Fatalf("custom Gemini overlay lost tool-call mapping: %+v,%t", mapping, ok)
	}

	bad := config.BuiltInProtocolProfileCatalog()
	bad["bad-tool-wire"] = config.ProtocolProfileConf{
		Base: "openai",
		Normalization: config.ProtocolNormalizationConf{ToolCalls: map[string]config.ProtocolToolCallMappingConf{
			string(config.OperationChatCompletions): {Wire: "unknown"},
		}},
	}
	if _, err := config.ResolveProtocolProfile("bad-tool-wire", "", bad); err == nil || !strings.Contains(err.Error(), "invalid wire") {
		t.Fatalf("expected invalid tool-call wire error, got %v", err)
	}
}

func TestProtocolProfileOverlayRejected(t *testing.T) {
	t.Run("cycle", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"a": {Base: "b", Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
			"b": {Base: "a", Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://b/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
		}
		_, err := config.ResolveProtocolProfile("a", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "cycle") {
			t.Fatalf("expected cycle error, got %v", err)
		}
	})

	t.Run("unknown_base", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"overlay": {Base: "nonexistent", Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
		}
		_, err := config.ResolveProtocolProfile("overlay", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "unknown") {
			t.Fatalf("expected unknown base error, got %v", err)
		}
	})

	t.Run("conflicting_driver", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"base":    {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{"chat"}},
			"overlay": {Base: "base", Driver: config.ProtocolDriverAnthropicMessages, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m", "messages": "/msg"}, Capabilities: []string{"messages"}},
		}
		_, err := config.ResolveProtocolProfile("overlay", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "driver") {
			t.Fatalf("expected driver conflict error, got %v", err)
		}
	})

	t.Run("invalid_operation", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"messages": "/msg"}, Capabilities: []string{"chat"}},
		}
		_, err := config.ResolveProtocolProfile("bad", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "messages") {
			t.Fatalf("expected invalid operation error, got %v", err)
		}
	})

	t.Run("invalid_auth_header", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m", "chat_completions": "/chat"}, Auth: config.ProtocolAuthConf{Header: ""}, Capabilities: []string{"models", "chat"}},
		}
		_, err := config.ResolveProtocolProfile("bad", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "auth") {
			t.Fatalf("expected auth validation error, got %v", err)
		}
	})

	t.Run("empty_capabilities", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "/m"}, Capabilities: []string{}},
		}
		_, err := config.ResolveProtocolProfile("bad", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "capabilities") {
			t.Fatalf("expected capabilities error, got %v", err)
		}
	})

	t.Run("malformed_absolute_url", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "://not-a-url"}, Capabilities: []string{"chat"}},
		}
		_, err := config.ResolveProtocolProfile("bad", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "operation") {
			t.Fatalf("expected URL validation error, got %v", err)
		}
	})

	t.Run("absolute_url_without_host", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"bad": {
				Driver:       config.ProtocolDriverOpenAIChat,
				BaseURL:      "https://a/v1",
				Operations:   map[string]string{"chat_completions": "https:///v1/chat/completions"},
				Auth:         config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
				Capabilities: []string{"chat"},
			},
		}
		_, err := config.ResolveProtocolProfile("bad", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "absolute http(s) URL") {
			t.Fatalf("expected absolute URL host validation error, got %v", err)
		}
	})

	t.Run("relative_path_no_leading_slash", func(t *testing.T) {
		catalog := map[string]config.ProtocolProfileConf{
			"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "models"}, Capabilities: []string{"chat"}},
		}
		_, err := config.ResolveProtocolProfile("bad", "", catalog)
		if err == nil || !strings.Contains(err.Error(), "leading") {
			t.Fatalf("expected leading slash error, got %v", err)
		}
	})
}

func TestLegacyProviderAliasProfileNormalization(t *testing.T) {
	cases := []struct {
		legacyType string
		wantID     string
	}{
		{"vllm", "openai"},
		{"openai_api", "openai"},
		{"openai_compat", "openai"},
		{"vllm-mlx", "openai"},
		{"lemonade", "openai"},
		{"sglang", "openai"},
		{"seulgivibe_claude", "seulgi_messages"},
		{"seulgivibe_openai", "seulgi_chat"},
		{"ollama", ""},
		{"unknown_type", ""},
	}
	for _, c := range cases {
		got := config.LegacyProviderTypeToProfile(c.legacyType)
		if got != c.wantID {
			t.Errorf("LegacyProviderTypeToProfile(%q) = %q, want %q", c.legacyType, got, c.wantID)
		}
	}
}

func TestLegacyProviderAliasResolvesToBuiltIn(t *testing.T) {
	// No explicit selector: legacy type should normalize to the built-in.
	resolved, err := config.ResolveProtocolProfile("", "vllm", config.BuiltInProtocolProfiles)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}
	if resolved.ID != "openai" {
		t.Errorf("ID = %q, want %q", resolved.ID, "openai")
	}
	if resolved.Driver != config.ProtocolDriverOpenAIChat {
		t.Errorf("Driver = %q, want %q", resolved.Driver, config.ProtocolDriverOpenAIChat)
	}
}

func TestExplicitProfilePrecedenceOverLegacyType(t *testing.T) {
	// Explicit selector should win over legacy type mapping.
	resolved, err := config.ResolveProtocolProfile("anthropic", "vllm", config.BuiltInProtocolProfiles)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}
	if resolved.ID != "anthropic" {
		t.Errorf("ID = %q, want %q", resolved.ID, "anthropic")
	}
	if resolved.Driver != config.ProtocolDriverAnthropicMessages {
		t.Errorf("Driver = %q, want %q", resolved.Driver, config.ProtocolDriverAnthropicMessages)
	}
}

func TestProtocolProfileMultiLevelOverlay(t *testing.T) {
	catalog := map[string]config.ProtocolProfileConf{
		"l1": {
			Driver:       config.ProtocolDriverOpenAIChat,
			BaseURL:      "https://l1/v1",
			Operations:   map[string]string{"models": "/models"},
			Auth:         config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
			Capabilities: []string{"models", "chat"},
		},
		"l2": {
			Base:       "l1",
			BaseURL:    "https://l2/v1",
			Operations: map[string]string{"chat_completions": "/chat/completions"},
		},
		"l3": {
			Base:         "l2",
			Capabilities: []string{"models", "chat", "streaming"},
		},
	}
	resolved, err := config.ResolveProtocolProfile("l3", "", catalog)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}
	if resolved.BaseURL != "https://l2/v1" {
		t.Errorf("BaseURL = %q, want %q", resolved.BaseURL, "https://l2/v1")
	}
	if _, ok := resolved.Operations["models"]; !ok {
		t.Error("expected inherited operation models")
	}
	if _, ok := resolved.Operations["chat_completions"]; !ok {
		t.Error("expected inherited operation chat_completions")
	}
	if len(resolved.Capabilities) != 3 {
		t.Errorf("Capabilities = %v, want 3", resolved.Capabilities)
	}
}

func TestProtocolProfileCustomOverlay(t *testing.T) {
	catalog := map[string]config.ProtocolProfileConf{
		"openai": config.BuiltInProtocolProfiles["openai"],
		"my-proxy": {
			Base:    "openai",
			BaseURL: "https://my-proxy.example.invalid/v1",
		},
	}
	resolved, err := config.ResolveProtocolProfile("my-proxy", "", catalog)
	if err != nil {
		t.Fatalf("ResolveProtocolProfile: %v", err)
	}
	if resolved.BaseURL != "https://my-proxy.example.invalid/v1" {
		t.Errorf("BaseURL = %q, want overridden value", resolved.BaseURL)
	}
	if resolved.ID != "my-proxy" {
		t.Errorf("ID = %q, want %q", resolved.ID, "my-proxy")
	}
}

// --- S03: Built-in catalog admission ---

func TestBuiltInProtocolProfileCatalog(t *testing.T) {
	expectedIDs := []string{
		"anthropic", "gemini", "glm", "glm_coding", "grok", "kimi", "minimax_chat", "minimax_messages",
		"mimo_chat", "mimo_messages",
		"openai", "seulgi_chat", "seulgi_messages",
	}
	for _, id := range expectedIDs {
		if _, ok := config.BuiltInProtocolProfiles[id]; !ok {
			t.Fatalf("built-in profile %q not found", id)
		}
		resolved, err := config.ResolveProtocolProfile(id, "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatalf("ResolveProtocolProfile(%q): %v", id, err)
		}
		if resolved.ID != id {
			t.Errorf("profile %q: ID = %q, want %q", id, resolved.ID, id)
		}
		if resolved.Base != "" {
			t.Errorf("profile %q: Base should be empty in concrete snapshot, got %q", id, resolved.Base)
		}
	}

	// Verify all expected IDs are present
	for _, id := range expectedIDs {
		if _, ok := config.BuiltInProtocolProfiles[id]; !ok {
			t.Errorf("expected built-in profile %q", id)
		}
	}
}

func TestBuiltInProtocolProfileOperations(t *testing.T) {
	t.Run("openai_has_chat_completions", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		if _, ok := r.Operations["chat_completions"]; !ok {
			t.Error("openai should declare chat_completions")
		}
		if _, ok := r.Operations["responses"]; !ok {
			t.Error("openai should declare responses")
		}
	})

	t.Run("anthropic_has_messages", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		if _, ok := r.Operations["messages"]; !ok {
			t.Error("anthropic should declare messages")
		}
		if _, ok := r.Operations["chat_completions"]; ok {
			t.Error("anthropic should NOT declare chat_completions")
		}
	})

	t.Run("seulgi_messages_has_messages", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("seulgi_messages", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		if _, ok := r.Operations["messages"]; !ok {
			t.Error("seulgi_messages should declare messages")
		}
	})

	t.Run("seulgi_chat_has_chat_completions", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("seulgi_chat", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		if _, ok := r.Operations["chat_completions"]; !ok {
			t.Error("seulgi_chat should declare chat_completions")
		}
	})
}

func TestBuiltInProtocolProfileURLs(t *testing.T) {
	t.Run("anthropic_absolute_path", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		url, err := r.ResolveOperationURL("messages")
		if err != nil {
			t.Fatal(err)
		}
		if !strings.HasSuffix(url, "/v1/messages") {
			t.Errorf("expected /v1/messages suffix, got %q", url)
		}
	})

	t.Run("gemini_v1beta_openai", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("gemini", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		url, err := r.ResolveOperationURL("chat_completions")
		if err != nil {
			t.Fatal(err)
		}
		if !strings.HasSuffix(url, "/v1beta/openai/chat/completions") {
			t.Errorf("expected /v1beta/openai/chat/completions suffix, got %q", url)
		}
	})

	t.Run("glm_api_paas_v4", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("glm", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		url, err := r.ResolveOperationURL("chat_completions")
		if err != nil {
			t.Fatal(err)
		}
		if !strings.HasSuffix(url, "/api/paas/v4/chat/completions") {
			t.Errorf("expected /api/paas/v4/chat/completions suffix, got %q", url)
		}
	})

	t.Run("glm_coding_api_coding_paas_v4", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("glm_coding", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		url, err := r.ResolveOperationURL("chat_completions")
		if err != nil {
			t.Fatal(err)
		}
		if !strings.HasSuffix(url, "/api/coding/paas/v4/chat/completions") {
			t.Errorf("expected /api/coding/paas/v4/chat/completions suffix, got %q", url)
		}
	})

	t.Run("glm_coding_models_url", func(t *testing.T) {
		r, err := config.ResolveProtocolProfile("glm_coding", "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatal(err)
		}
		url, err := r.ResolveOperationURL("models")
		if err != nil {
			t.Fatal(err)
		}
		if !strings.HasSuffix(url, "/api/coding/paas/v4/models") {
			t.Errorf("expected /api/coding/paas/v4/models suffix, got %q", url)
		}
	})
}

func TestProtocolProfileUnsupportedOperationRejected(t *testing.T) {
	r, err := config.ResolveProtocolProfile("anthropic", "", config.BuiltInProtocolProfiles)
	if err != nil {
		t.Fatal(err)
	}
	_, err = r.ResolveOperationURL("chat_completions")
	if err == nil || !strings.Contains(err.Error(), "not supported") {
		t.Fatalf("expected unsupported operation error, got %v", err)
	}
}

func TestProtocolProfileResolveOperationURLAbsolute(t *testing.T) {
	catalog := map[string]config.ProtocolProfileConf{
		"custom": {
			Driver:  config.ProtocolDriverOpenAIChat,
			BaseURL: "https://base.example.com/v1",
			Operations: map[string]string{
				"models":           "https://override.example.com/v1/models",
				"chat_completions": "/chat/completions",
			},
			Auth:         config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
			Capabilities: []string{"models", "chat"},
		},
	}
	r, err := config.ResolveProtocolProfile("custom", "", catalog)
	if err != nil {
		t.Fatal(err)
	}
	url, err := r.ResolveOperationURL("models")
	if err != nil {
		t.Fatal(err)
	}
	if url != "https://override.example.com/v1/models" {
		t.Errorf("expected absolute URL unchanged, got %q", url)
	}
}

func TestProtocolProfileClone(t *testing.T) {
	r, err := config.ResolveProtocolProfile("openai", "", config.BuiltInProtocolProfiles)
	if err != nil {
		t.Fatal(err)
	}
	clone := r.Clone()
	if clone.ID != r.ID {
		t.Errorf("ID mismatch: %q vs %q", clone.ID, r.ID)
	}
	clone.Operations["injected"] = "/injected"
	if _, ok := r.Operations["injected"]; ok {
		t.Error("clone mutation affected original")
	}
}

func TestProtocolProfileProviderEndpointOverlay(t *testing.T) {
	cfg, err := loadProtocolProfileYAML(t, `
nodes:
  - id: node-profile
    providers:
      - id: provider-profile
        type: vllm
        category: api
        endpoint: http://127.0.0.1:18080/v1/
        base_url: http://127.0.0.1:18081/ignored
        models: [upstream-model]
models:
  - id: canonical-model
    providers:
      provider-profile: upstream-model
`)
	if err != nil {
		t.Fatalf("LoadEdge: %v", err)
	}
	profile := cfg.Nodes[0].Providers[0].RuntimeProfile
	if profile == nil || profile.BaseURL != "http://127.0.0.1:18080/v1" {
		t.Fatalf("runtime profile base_url = %#v", profile)
	}
	url, err := profile.ResolveOperationURL(string(config.OperationChatCompletions))
	if err != nil || url != "http://127.0.0.1:18080/v1/chat/completions" {
		t.Fatalf("resolved chat URL = %q, err=%v", url, err)
	}
	fresh := config.BuiltInProtocolProfileCatalog()["openai"]
	if fresh.BaseURL != "https://api.openai.com/v1" {
		t.Fatalf("provider endpoint mutated built-in: %q", fresh.BaseURL)
	}
	rootCfg, err := loadProtocolProfileYAML(t, `
nodes:
  - id: node-root
    providers:
      - id: provider-root
        type: lemonade
        category: api
        endpoint: http://127.0.0.1:18082
        models: [upstream-model]
models:
  - id: root-model
    providers: {provider-root: upstream-model}
`)
	if err != nil {
		t.Fatalf("LoadEdge root endpoint: %v", err)
	}
	rootProfile := rootCfg.Nodes[0].Providers[0].RuntimeProfile
	if rootProfile == nil || rootProfile.BaseURL != "http://127.0.0.1:18082/v1" {
		t.Fatalf("root endpoint did not retain profile prefix: %#v", rootProfile)
	}
}

func TestProtocolProfileCatalogCollisionRejected(t *testing.T) {
	_, err := loadProtocolProfileYAML(t, `
protocol_profiles:
  openai:
    driver: openai_chat
    base_url: https://collision.example.invalid/v1
    operations: {chat_completions: /chat/completions}
    auth: {header: Authorization, scheme: Bearer}
    capabilities: [chat]
`)
	if err == nil || !strings.Contains(err.Error(), "reserved") {
		t.Fatalf("expected reserved built-in collision error, got %v", err)
	}
}

func TestProtocolProfileCapabilityMatrixRejected(t *testing.T) {
	base := config.ProtocolProfileConf{
		Driver:       config.ProtocolDriverOpenAIChat,
		BaseURL:      "https://matrix.example.invalid/v1",
		Operations:   map[string]string{"chat_completions": "/chat/completions"},
		Auth:         config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
		Capabilities: []string{"chat"},
	}
	for _, tc := range []struct {
		name string
		edit func(*config.ProtocolProfileConf)
		want string
	}{
		{name: "unknown_capability", edit: func(p *config.ProtocolProfileConf) { p.Capabilities = append(p.Capabilities, "telepathy") }, want: "not recognized"},
		{name: "operation_without_capability", edit: func(p *config.ProtocolProfileConf) { p.Operations["models"] = "/models" }, want: "requires capability"},
		{name: "wrong_driver_operation", edit: func(p *config.ProtocolProfileConf) { p.Operations["messages"] = "/messages" }, want: "not supported"},
		{name: "raw_header_with_scheme", edit: func(p *config.ProtocolProfileConf) {
			p.Auth = config.ProtocolAuthConf{Header: "X-Api-Key", Scheme: "Bearer"}
		}, want: "raw credential"},
		{name: "authorization_without_bearer", edit: func(p *config.ProtocolProfileConf) { p.Auth.Scheme = "Token" }, want: "Bearer"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			profile := base
			profile.Operations = map[string]string{"chat_completions": "/chat/completions"}
			profile.Capabilities = append([]string(nil), base.Capabilities...)
			tc.edit(&profile)
			_, err := config.ResolveProtocolProfile("bad", "", map[string]config.ProtocolProfileConf{"bad": profile})
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("expected %q error, got %v", tc.want, err)
			}
		})
	}
}

func TestProtocolProfileChatOnlyTokenCounter(t *testing.T) {
	base := func(profile, counter string) string {
		return `
nodes:
  - id: node-counter
    providers:
      - id: provider-counter
        type: openai_api
        category: api
        profile: ` + profile + `
        endpoint: http://127.0.0.1:18080/v1
        models: [upstream-model]
models:
  - id: canonical-model
    providers:
      provider-counter: upstream-model
    token_counter:
` + counter
	}
	if _, err := loadProtocolProfileYAML(t, base("openai", "      mode: deterministic\n")); err != nil {
		t.Fatalf("Chat deterministic counter: %v", err)
	}
	for _, tc := range []struct {
		name, profile, counter, want string
	}{
		{name: "messages", profile: "anthropic", counter: "      mode: deterministic\n", want: "Chat-only"},
		{name: "estimate_range", profile: "openai", counter: "      mode: estimate\n      per_1k_input: 1001\n", want: "between 1 and 1000"},
		{name: "deterministic_parameter", profile: "openai", counter: "      mode: deterministic\n      per_1k_input: 250\n", want: "must be 0"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			_, err := loadProtocolProfileYAML(t, base(tc.profile, tc.counter))
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("expected %q error, got %v", tc.want, err)
			}
		})
	}
	_, err := loadProtocolProfileYAML(t, `
protocol_profiles:
  responses_custom:
    driver: openai_responses
    base_url: https://responses.example.invalid/v1
    operations: {responses: /responses}
    auth: {header: Authorization, scheme: Bearer}
    capabilities: [responses]
nodes:
  - id: node-responses-counter
    providers:
      - id: responses-counter
        type: openai_api
        category: api
        profile: responses_custom
        models: [upstream-response]
models:
  - id: canonical-response
    providers: {responses-counter: upstream-response}
    token_counter: {mode: deterministic}
`)
	if err == nil || !strings.Contains(err.Error(), "Chat-only") {
		t.Fatalf("expected Responses profile token-counter rejection, got %v", err)
	}
}

func TestBuiltInProtocolProfileCatalogImmutable(t *testing.T) {
	first := config.BuiltInProtocolProfileCatalog()
	openai := first["openai"]
	openai.Operations[string(config.OperationChatCompletions)] = "/mutated"
	openai.Capabilities[0] = "mutated"
	first["openai"] = openai
	fresh := config.BuiltInProtocolProfileCatalog()["openai"]
	if fresh.Operations[string(config.OperationChatCompletions)] != "/chat/completions" || fresh.Capabilities[0] == "mutated" {
		t.Fatalf("built-in catalog mutation leaked: %+v", fresh)
	}
}

// TestProtocolProfileHeaderValidation covers the RFC 9110 field-name token
// grammar independently from Go's permissive MIME canonicalization helpers.
func TestProtocolProfileHeaderValidation(t *testing.T) {
	validProfile := func(header string) config.ProtocolProfileConf {
		return config.ProtocolProfileConf{
			Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://example.invalid/v1",
			Operations: map[string]string{"chat_completions": "/chat/completions"},
			Auth:       config.ProtocolAuthConf{Header: header}, Capabilities: []string{"chat"},
		}
	}
	for _, tc := range []struct {
		name, header string
		valid        bool
	}{
		{name: "token_punctuation", header: "X!#$%&'*+-.^_`|~Key", valid: true},
		{name: "empty", header: ""},
		{name: "leading_space", header: " X-Key"},
		{name: "trailing_space", header: "X-Key "},
		{name: "embedded_space", header: "X Key"},
		{name: "colon", header: "X:Key"},
		{name: "tab", header: "X\tKey"},
		{name: "control", header: "X\x01Key"},
		{name: "non_ascii", header: "X-Kéy"},
		{name: "separator", header: "X(Key)"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			profile := validProfile(tc.header)
			_, err := config.ResolveProtocolProfile("header-test", "", map[string]config.ProtocolProfileConf{"header-test": profile})
			if tc.valid && err != nil {
				t.Fatalf("valid header rejected: %v", err)
			}
			if !tc.valid && (err == nil || !strings.Contains(err.Error(), "auth")) {
				t.Fatalf("invalid header %q was not rejected: %v", tc.header, err)
			}
		})
	}
}

// TestProtocolProfileBuiltInAuthMatrix is intentionally literal so catalog
// drift cannot update the expected values by sharing production data. MiniMax
// and MiMo Anthropic-compatible auth was rechecked against official provider
// documentation on 2026-08-01:
// https://platform.minimax.io/docs/token-plan/other-tools
// https://mimo.mi.com/docs/en-US/quick-start/summary/first-api-call
func TestProtocolProfileBuiltInAuthMatrix(t *testing.T) {
	type wantAuth struct{ header, scheme string }
	want := map[string]wantAuth{
		"openai": {"Authorization", "Bearer"}, "gemini": {"Authorization", "Bearer"},
		"anthropic": {"x-api-key", ""}, "glm": {"Authorization", "Bearer"},
		"glm_coding": {"Authorization", "Bearer"},
		"kimi":       {"Authorization", "Bearer"}, "minimax_chat": {"Authorization", "Bearer"},
		"minimax_messages": {"Authorization", "Bearer"}, "mimo_chat": {"Authorization", "Bearer"},
		"mimo_messages": {"api-key", ""}, "grok": {"Authorization", "Bearer"},
		"seulgi_chat": {"Authorization", "Bearer"}, "seulgi_messages": {"x-api-key", ""},
	}
	if len(config.BuiltInProtocolProfiles) != len(want) {
		t.Fatalf("built-in profile count = %d, want %d", len(config.BuiltInProtocolProfiles), len(want))
	}
	for id, expected := range want {
		resolved, err := config.ResolveProtocolProfile(id, "", config.BuiltInProtocolProfiles)
		if err != nil {
			t.Fatalf("ResolveProtocolProfile(%q): %v", id, err)
		}
		if resolved.Auth.Header != expected.header || resolved.Auth.Scheme != expected.scheme {
			t.Errorf("%s auth = {%q %q}, want {%q %q}", id, resolved.Auth.Header, resolved.Auth.Scheme, expected.header, expected.scheme)
		}
	}
}

// TestBuiltInGLMProtocolProfiles asserts that both GLM profiles carry the
// expected capabilities, lack Responses, and expose only models+chat operations.
// Official docs rechecked on 2026-08-02:
// https://docs.z.ai/api-reference/introduction
// https://docs.z.ai/api-reference/llm/chat-completion
// https://docs.z.ai/scenario-example/develop-tools/others
func TestBuiltInGLMProtocolProfiles(t *testing.T) {
	for _, id := range []string{"glm", "glm_coding"} {
		t.Run(id, func(t *testing.T) {
			r, err := config.ResolveProtocolProfile(id, "", config.BuiltInProtocolProfiles)
			if err != nil {
				t.Fatal(err)
			}
			for _, want := range []string{"models", "chat", "streaming", "tool_calling"} {
				if !r.HasCapability(want) {
					t.Errorf("%s: missing capability %q", id, want)
				}
			}
			if r.HasCapability("responses") {
				t.Errorf("%s: must not advertise responses capability", id)
			}
			if _, ok := r.Operations[string(config.OperationResponses)]; ok {
				t.Errorf("%s: must not declare responses operation", id)
			}
			for _, op := range []config.ProtocolOperation{config.OperationModels, config.OperationChatCompletions} {
				if _, ok := r.Operations[string(op)]; !ok {
					t.Errorf("%s: missing operation %q", id, op)
				}
			}
		})
	}
}

// TestGLMProfilesRemainDistinctThroughProviderPoolConfig loads a provider-pool
// mapping with two distinct external model IDs that route to two distinct
// provider IDs and two distinct GLM profiles, then asserts no cross-mapping
// and both map to the same upstream model.
// Official endpoint distinction rechecked on 2026-08-02:
// https://docs.z.ai/scenario-example/develop-tools/others
func TestGLMProfilesRemainDistinctThroughProviderPoolConfig(t *testing.T) {
	tmpDir := t.TempDir()
	yaml := `
models:
  - id: "glm-5.1-api"
    providers:
      glm-api: "glm-5.1"
  - id: "glm-5.1-coding"
    providers:
      glm-coding: "glm-5.1"
nodes:
  - id: "node-glm"
    providers:
      - id: "glm-api"
        type: "openai_api"
        category: "api"
        profile: "glm"
        endpoint: "https://api.z.ai/api/paas/v4"
        models: ["glm-5.1"]
        capacity: 1
      - id: "glm-coding"
        type: "openai_api"
        category: "api"
        profile: "glm_coding"
        endpoint: "https://api.z.ai/api/coding/paas/v4"
        models: ["glm-5.1"]
        capacity: 1
`
	path := filepath.Join(tmpDir, "edge.yaml")
	if err := os.WriteFile(path, []byte(yaml), 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}
	cfg, err := config.LoadEdge(path)
	if err != nil {
		t.Fatalf("LoadEdge: %v", err)
	}
	if len(cfg.Models) != 2 {
		t.Fatalf("expected 2 models, got %d", len(cfg.Models))
	}
	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 providers, got %d", len(cfg.Nodes[0].Providers))
	}

	var apiProv, codingProv *config.NodeProviderConf
	for i := range cfg.Nodes[0].Providers {
		p := &cfg.Nodes[0].Providers[i]
		if p.ID == "glm-api" {
			apiProv = p
		} else if p.ID == "glm-coding" {
			codingProv = p
		}
	}
	if apiProv == nil {
		t.Fatal("glm-api provider missing")
	}
	if codingProv == nil {
		t.Fatal("glm-coding provider missing")
	}
	if apiProv.Profile != "glm" {
		t.Errorf("glm-api profile = %q, want %q", apiProv.Profile, "glm")
	}
	if codingProv.Profile != "glm_coding" {
		t.Errorf("glm-coding profile = %q, want %q", codingProv.Profile, "glm_coding")
	}

	if len(cfg.Models[0].Providers) != 1 {
		t.Fatalf("glm-5.1-api: expected 1 provider mapping, got %d", len(cfg.Models[0].Providers))
	}
	if len(cfg.Models[1].Providers) != 1 {
		t.Fatalf("glm-5.1-coding: expected 1 provider mapping, got %d", len(cfg.Models[1].Providers))
	}
	if cfg.Models[0].Providers["glm-api"] != "glm-5.1" {
		t.Errorf("glm-5.1-api provider served = %q, want %q", cfg.Models[0].Providers["glm-api"], "glm-5.1")
	}
	if cfg.Models[1].Providers["glm-coding"] != "glm-5.1" {
		t.Errorf("glm-5.1-coding provider served = %q, want %q", cfg.Models[1].Providers["glm-coding"], "glm-5.1")
	}

	// Resolved runtime profiles must be distinct.
	apiProfile, err := config.ResolveProtocolProfile("glm", "", config.BuiltInProtocolProfiles)
	if err != nil {
		t.Fatal(err)
	}
	codingProfile, err := config.ResolveProtocolProfile("glm_coding", "", config.BuiltInProtocolProfiles)
	if err != nil {
		t.Fatal(err)
	}
	if apiProfile.BaseURL == codingProfile.BaseURL {
		t.Errorf("glm and glm_coding must resolve to different base URLs, both got %q", apiProfile.BaseURL)
	}
}

func TestProtocolProfileAdapterBackingResolution(t *testing.T) {
	cfg, err := loadProtocolProfileYAML(t, `
nodes:
  - id: node-backed
    adapters:
      openai_compat_instances:
        - name: upstream
          enabled: true
          provider: lemonade
          endpoint: http://127.0.0.1:18080
    providers:
      - id: profile-provider
        type: lemonade
        category: api
        adapter: upstream
        profile: openai
        models: [served]
        capacity: 1
models:
  - id: canonical
    providers: {profile-provider: served}
`)
	if err != nil {
		t.Fatalf("LoadEdge: %v", err)
	}
	profile := cfg.Nodes[0].Providers[0].RuntimeProfile
	if profile == nil || profile.BaseURL != "http://127.0.0.1:18080/v1" {
		t.Fatalf("backing endpoint was not applied: %#v", profile)
	}
}

func TestProtocolProfileAdapterBackingRejected(t *testing.T) {
	for _, tc := range []struct {
		name, adapters, ref, want string
	}{
		{name: "missing", ref: "missing", adapters: "      openai_compat_instances: []", want: "missing"},
		{name: "disabled", ref: "backing", adapters: "      openai_compat_instances:\n        - {name: backing, enabled: false, endpoint: http://127.0.0.1:1}", want: "disabled"},
		{name: "ambiguous", ref: "backing", adapters: "      vllm_instances:\n        - {name: backing, enabled: true, endpoint: http://127.0.0.1:1}\n      openai_compat_instances:\n        - {name: backing, enabled: true, endpoint: http://127.0.0.1:2}", want: "ambiguous"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			_, err := loadProtocolProfileYAML(t, `
nodes:
  - id: node-bad-backing
    adapters:
`+tc.adapters+`
    providers:
      - id: profile-provider
        type: openai_compat
        category: api
        adapter: `+tc.ref+`
        profile: openai
        models: [served]
        capacity: 1
`)
			if err == nil || !strings.Contains(err.Error(), tc.want) || !strings.Contains(err.Error(), "profile-provider") || !strings.Contains(err.Error(), tc.ref) {
				t.Fatalf("expected contextual %q error, got %v", tc.want, err)
			}
		})
	}
}

func loadProtocolProfileYAML(t *testing.T, contents string) (*config.EdgeConfig, error) {
	t.Helper()
	path := filepath.Join(t.TempDir(), "edge.yaml")
	if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
		t.Fatalf("write config: %v", err)
	}
	return config.LoadEdge(path)
}
