package openai

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	edgeservice "iop/apps/edge/internal/service"
	"iop/packages/go/config"
	iop "iop/proto/gen/iop"
)

// TestResponsesProviderPoolTunnelSelectionUsesPassthrough verifies that when a
// provider-pool catalog match is resolved and the selected provider is an
// OpenAI-compatible type (tunnel executionPath), the Responses handler
// dispatches via tunnel passthrough with the raw body preserved (model rewrite
// only). This mirrors TestChatCompletionsProviderPoolTunnelSelectionUsesPassthrough
// for /v1/responses.
func TestResponsesProviderPoolTunnelSelectionUsesPassthrough(t *testing.T) {
	frames := make(chan *iop.ProviderTunnelFrame, 3)
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200}
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(`{"ok":true}`)}
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true}
	close(frames)

	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
		tunnelFrames:     frames,
	}
	catalog := []config.ModelCatalogEntry{
		{ID: "vllm-resp-model", Providers: map[string]string{"prov-vllm": "Qwen3-35B"}},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"vllm-resp-model",
		"input":"hello world"
	}`))
	w := httptest.NewRecorder()
	srv.handleResponses(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	// Tunnel passthrough must use SubmitProviderPool tunnel path.
	tunnelReqs := fake.tunnelReqsSnapshot()
	reqs := fake.reqsSnapshot()
	if len(tunnelReqs) != 1 {
		t.Fatalf("expected 1 tunnel dispatch for vLLM path, got %d", len(tunnelReqs))
	}
	if len(reqs) != 0 {
		t.Fatalf("expected 0 normalized SubmitRun for vLLM tunnel path, got %d", len(reqs))
	}
	if !tunnelReqs[0].ProviderPool {
		t.Error("tunnel dispatch must have ProviderPool=true")
	}
	if tunnelReqs[0].Path != "/v1/responses" {
		t.Fatalf("tunnel path: got %q want /v1/responses", tunnelReqs[0].Path)
	}
}

// TestResponsesProviderPoolOllamaSelectionUsesNormalizedRun verifies that when a
// provider-pool catalog match is resolved and the selected provider is an
// Ollama type (normalized executionPath), the Responses handler dispatches via
// normalized RunEvent path with the decoded prompt/input, not via tunnel. This
// mirrors TestChatCompletionsProviderPoolOllamaSelectionUsesNormalizedRun for
// /v1/responses.
func TestResponsesProviderPoolOllamaSelectionUsesNormalizedRun(t *testing.T) {
	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
	}
	catalog := []config.ModelCatalogEntry{
		{ID: "ollama-resp-model", Providers: map[string]string{"prov-ollama": "qwen3.6:35b"}},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"ollama-resp-model",
		"input":"hello world"
	}`))
	w := httptest.NewRecorder()
	srv.handleResponses(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	// Normalized Ollama path must use SubmitProviderPool normalized path.
	reqs := fake.reqsSnapshot()
	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(reqs) != 1 {
		t.Fatalf("expected 1 normalized SubmitRun for Ollama path, got %d", len(reqs))
	}
	if len(tunnelReqs) != 0 {
		t.Fatalf("expected 0 tunnel dispatch for Ollama normalized path, got %d", len(tunnelReqs))
	}
	if !reqs[0].ProviderPool {
		t.Error("normalized dispatch must have ProviderPool=true")
	}
	if reqs[0].ModelGroupKey != "ollama-resp-model" {
		t.Fatalf("expected model group key %q, got %q", "ollama-resp-model", reqs[0].ModelGroupKey)
	}
}

// TestResponsesMixedProviderPoolTunnelSelection verifies S05: a mixed
// provider-pool model group that selects an OpenAI-compatible (vLLM) provider
// dispatches via tunnel passthrough for /v1/responses.
func TestResponsesMixedProviderPoolTunnelSelection(t *testing.T) {
	tunnelFrames := make(chan *iop.ProviderTunnelFrame, 3)
	tunnelFrames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200}
	tunnelFrames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(`{"id":"resp-mixed","object":"response"}`)}
	tunnelFrames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true}
	close(tunnelFrames)

	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
		tunnelFrames:     tunnelFrames,
	}
	catalog := []config.ModelCatalogEntry{
		{
			ID: "mixed-resp-model",
			Providers: map[string]string{
				"prov-vllm":   "served-vllm",
				"prov-ollama": "served-ollama",
			},
		},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"mixed-resp-model",
		"input":"hello mixed"
	}`))
	w := httptest.NewRecorder()
	srv.handleResponses(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(tunnelReqs) != 1 {
		t.Fatalf("expected 1 tunnel dispatch, got %d", len(tunnelReqs))
	}
	if !tunnelReqs[0].ProviderPool {
		t.Error("tunnel request must have ProviderPool=true")
	}
	if tunnelReqs[0].Path != "/v1/responses" {
		t.Fatalf("tunnel path: got %q want /v1/responses", tunnelReqs[0].Path)
	}

	runReqs := fake.reqsSnapshot()
	if len(runReqs) != 0 {
		t.Errorf("expected 0 normalized SubmitRun calls, got %d", len(runReqs))
	}
}

// TestResponsesMixedProviderPoolOllamaSelection verifies S05: when the mixed
// model group selects an Ollama provider, the request is dispatched over the
// normalized path (SubmitRun), and no tunnel dispatch is issued for /v1/responses.
func TestResponsesMixedProviderPoolOllamaSelection(t *testing.T) {
	events := bufferedRunEvents(
		&iop.RunEvent{Type: "delta", Delta: "ollama-resp"},
		&iop.RunEvent{Type: "complete"},
	)

	fake := &providerFakeRunService{
		fakeRunService: fakeRunService{
			events: events,
		},
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
	}
	catalog := []config.ModelCatalogEntry{
		{
			ID: "mixed-resp-model",
			Providers: map[string]string{
				"prov-vllm":   "served-vllm",
				"prov-ollama": "served-ollama",
			},
		},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"mixed-resp-model",
		"input":"hello mixed"
	}`))
	w := httptest.NewRecorder()
	srv.handleResponses(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(tunnelReqs) != 0 {
		t.Fatalf("expected 0 tunnel dispatches for Ollama selection, got %d", len(tunnelReqs))
	}

	runReqs := fake.reqsSnapshot()
	if len(runReqs) != 1 {
		t.Fatalf("expected 1 normalized SubmitRun, got %d", len(runReqs))
	}
	if runReqs[0].ModelGroupKey != "mixed-resp-model" {
		t.Errorf("normalized ModelGroupKey: got %q, want mixed-resp-model", runReqs[0].ModelGroupKey)
	}
	if !runReqs[0].ProviderPool {
		t.Error("normalized run must have ProviderPool=true")
	}
}

func TestChatCompletionsProviderPoolOllamaSelectionUsesNormalizedRun(t *testing.T) {
	runEvents := make(chan *iop.RunEvent, 2)
	runEvents <- &iop.RunEvent{Type: "complete"}
	close(runEvents)

	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
		poolRunFrames:    runEvents,
	}
	catalog := []config.ModelCatalogEntry{
		{ID: "ollama-model", Providers: map[string]string{"prov-ollama": "qwen3.6:35b"}},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"ollama-model",
		"messages":[{"role":"user","content":"hello"}]
	}`))
	w := httptest.NewRecorder()
	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	// Ollama-selected provider-pool should use normalized path (SubmitRun),
	// NOT tunnel passthrough.
	tunnelReqs := fake.tunnelReqsSnapshot()
	reqs := fake.reqsSnapshot()
	if len(tunnelReqs) != 0 {
		t.Fatalf("expected 0 tunnel dispatches for Ollama-normalized path, got %d", len(tunnelReqs))
	}
	if len(reqs) != 1 {
		t.Fatalf("expected exactly 1 normalized SubmitRun for Ollama path, got %d", len(reqs))
	}
}

// TestChatCompletionsProviderPoolTunnelSelectionUsesPassthrough verifies that
// when a provider-pool catalog match is resolved and the selected provider is
// an OpenAI-compatible type (tunnel executionPath), the Chat handler dispatches
// via tunnel passthrough.
func TestChatCompletionsProviderPoolTunnelSelectionUsesPassthrough(t *testing.T) {
	frames := make(chan *iop.ProviderTunnelFrame, 3)
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200}
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(`{"ok":true}`)}
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true}
	close(frames)

	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
		tunnelFrames:     frames,
	}
	catalog := []config.ModelCatalogEntry{
		{ID: "vllm-model", Providers: map[string]string{"prov-vllm": "Qwen3-35B"}},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"vllm-model",
		"messages":[{"role":"user","content":"hello"}]
	}`))
	w := httptest.NewRecorder()
	srv.handleChatCompletions(w, req)

	// OpenAI-compatible provider-pool should use tunnel passthrough.
	tunnelReqs := fake.tunnelReqsSnapshot()
	reqs := fake.reqsSnapshot()
	if len(tunnelReqs) != 1 {
		t.Fatalf("expected 1 tunnel dispatch for vLLM path, got %d", len(tunnelReqs))
	}
	if !tunnelReqs[0].ProviderPool {
		t.Error("tunnel dispatch must have ProviderPool=true")
	}
	if len(reqs) != 0 {
		t.Fatalf("expected 0 normalized SubmitRun for vLLM tunnel path, got %d", len(reqs))
	}
}

// TestChatCompletionsMixedProviderPoolTunnelSelection verifies S05: a mixed
// model group containing both OpenAI-compatible and Ollama providers dispatches
// the selected tunnel-capable provider over the provider tunnel (passthrough)
// path. Only one tunnel request is sent, no normalized SubmitRun is called.
func TestChatCompletionsMixedProviderPoolTunnelSelection(t *testing.T) {
	// Tunnel frames for the tunnel path.
	tunnelFrames := make(chan *iop.ProviderTunnelFrame, 3)
	tunnelFrames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200}
	tunnelFrames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(`{"id":"cmpl-mixed","choices":[{"index":0,"message":{"role":"assistant","content":"tunnel"}}],"finish_reason":"stop"}`)}
	tunnelFrames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true}
	close(tunnelFrames)

	// Fake service selects tunnel path (simulating vLLM provider selection).
	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
		tunnelFrames:     tunnelFrames,
	}

	// Mixed catalog: OpenAI-compatible (vLLM) + Ollama in same group.
	catalog := []config.ModelCatalogEntry{
		{
			ID: "mixed-model",
			Providers: map[string]string{
				"prov-vllm":   "served-vllm",
				"prov-ollama": "served-ollama",
			},
		},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"mixed-model",
		"messages":[{"role":"user","content":"hello"}]
	}`))
	w := httptest.NewRecorder()
	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	// Tunnel path: exactly 1 tunnel dispatch, 0 normalized SubmitRun.
	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(tunnelReqs) != 1 {
		t.Fatalf("expected 1 tunnel dispatch, got %d", len(tunnelReqs))
	}
	if !tunnelReqs[0].ProviderPool {
		t.Error("tunnel request must have ProviderPool=true")
	}
	if tunnelReqs[0].ModelGroupKey != "mixed-model" {
		t.Errorf("tunnel ModelGroupKey: got %q, want mixed-model", tunnelReqs[0].ModelGroupKey)
	}

	runReqs := fake.reqsSnapshot()
	if len(runReqs) != 0 {
		t.Errorf("expected 0 normalized SubmitRun calls, got %d", len(runReqs))
	}
}

// TestChatCompletionsMixedProviderPoolOllamaSelection verifies S05: when the
// mixed model group selects an Ollama provider, the request is dispatched over
// the normalized path (SubmitRun), and no tunnel dispatch is issued.
func TestChatCompletionsMixedProviderPoolOllamaSelection(t *testing.T) {
	// Normalized path events.
	events := bufferedRunEvents(
		&iop.RunEvent{Type: "delta", Delta: "ollama"},
		&iop.RunEvent{Type: "complete"},
	)

	// Fake service selects normalized path (simulating Ollama provider selection).
	fake := &providerFakeRunService{
		fakeRunService: fakeRunService{
			events: events,
		},
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
	}

	// Same mixed catalog: vLLM + Ollama.
	catalog := []config.ModelCatalogEntry{
		{
			ID: "mixed-model",
			Providers: map[string]string{
				"prov-vllm":   "served-vllm",
				"prov-ollama": "served-ollama",
			},
		},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"mixed-model",
		"messages":[{"role":"user","content":"hello"}]
	}`))
	w := httptest.NewRecorder()
	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	// Normalized path: 0 tunnel dispatches, 1 normalized SubmitRun.
	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(tunnelReqs) != 0 {
		t.Fatalf("expected 0 tunnel dispatches for Ollama selection, got %d", len(tunnelReqs))
	}

	runReqs := fake.reqsSnapshot()
	if len(runReqs) != 1 {
		t.Fatalf("expected 1 normalized SubmitRun, got %d", len(runReqs))
	}
	// Adapter/Target are rewritten by the service's admitted candidate; the
	// fake here records the pre-admission request so the handler-level path
	// selection (tunnel vs normalized) is the assertion surface.
	if runReqs[0].ModelGroupKey != "mixed-model" {
		t.Errorf("normalized ModelGroupKey: got %q, want mixed-model", runReqs[0].ModelGroupKey)
	}
	if !runReqs[0].ProviderPool {
		t.Error("normalized run must have ProviderPool=true")
	}
}

// TestChatCompletionsOllamaOnlyProviderPoolUsesNormalizedPath verifies S05:
// a model group containing only Ollama providers always dispatches over the
// normalized path. No tunnel request is ever issued.
func TestChatCompletionsOllamaOnlyProviderPoolUsesNormalizedPath(t *testing.T) {
	events := bufferedRunEvents(
		&iop.RunEvent{Type: "delta", Delta: "ollama-only"},
		&iop.RunEvent{Type: "complete"},
	)

	// Fake service selects normalized path.
	fake := &providerFakeRunService{
		fakeRunService: fakeRunService{
			events: events,
		},
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
	}

	// Ollama-only catalog.
	catalog := []config.ModelCatalogEntry{
		{
			ID: "ollama-only-model",
			Providers: map[string]string{
				"prov-ollama": "qwen3.6:35b",
			},
		},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"ollama-only-model",
		"messages":[{"role":"user","content":"hello"}]
	}`))
	w := httptest.NewRecorder()
	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	// Must not dispatch tunnel.
	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(tunnelReqs) != 0 {
		t.Fatalf("ollama-only must not dispatch tunnel, got %d", len(tunnelReqs))
	}

	// Must dispatch exactly one normalized run.
	runReqs := fake.reqsSnapshot()
	if len(runReqs) != 1 {
		t.Fatalf("expected 1 normalized SubmitRun, got %d", len(runReqs))
	}
	if runReqs[0].ModelGroupKey != "ollama-only-model" {
		t.Errorf("ollama-only ModelGroupKey: got %q, want ollama-only-model", runReqs[0].ModelGroupKey)
	}
	if !runReqs[0].ProviderPool {
		t.Error("ollama-only normalized run must have ProviderPool=true")
	}
}

// TestChatCompletionsProviderPoolTunnelPreservesUnknownFields verifies that
// when the provider-pool dispatch selects the tunnel path, the caller's raw
// body is forwarded with model rewrite only: unknown top-level fields
// (store, codex_trace, custom_provider_options, nested unknown object) and
// Codex/provider-specific fields survive into the tunnel request body. No
// IOP sideband key is injected into the pure response payload (SURFACE_FIELD).
func TestChatCompletionsProviderPoolTunnelPreservesUnknownFields(t *testing.T) {
	frames := make(chan *iop.ProviderTunnelFrame, 3)
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200}
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(`{"ok":true}`)}
	frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true}
	close(frames)
	fake := &providerFakeRunService{
		tunnelFrames:       frames,
		tunnelServedTarget: "served-model",
	}

	catalog := []config.ModelCatalogEntry{
		{ID: "pool-model", Providers: map[string]string{"prov-vllm": "served-model"}},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	reqBody := `{
		"model":"pool-model",
		"messages":[{"role":"user","content":"hello"}],
		"store":true,
		"parallel_tool_calls":true,
		"codex_trace":{"enabled":true},
		"custom_provider_options":{"timeout_ms":5000},
		"nested":{"unknown":{"deep":{"value":42}}},
		"thinking_token_budget":1024
	}`
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(reqBody))
	w := httptest.NewRecorder()
	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(tunnelReqs) != 1 {
		t.Fatalf("expected 1 tunnel dispatch, got %d", len(tunnelReqs))
	}

	// Parse the tunnel body as a JSON map and verify model rewrite + unknown preservation.
	tunnelBodies := fake.tunnelBodiesSnapshot()
	if len(tunnelBodies) != 1 {
		t.Fatalf("expected 1 tunnel body, got %d", len(tunnelBodies))
	}
	var bodyMap map[string]json.RawMessage
	if err := json.Unmarshal(tunnelBodies[0], &bodyMap); err != nil {
		t.Fatalf("decode tunnel body: %v body=%s", err, string(tunnelBodies[0]))
	}

	// Model must be rewritten to the served target.
	var bodyModel string
	if err := json.Unmarshal(bodyMap["model"], &bodyModel); err != nil {
		t.Fatalf("decode model field: %v", err)
	}
	if bodyModel != "served-model" {
		t.Errorf("model rewrite: got %q, want served-model", bodyModel)
	}

	// Known fields must survive.
	if _, ok := bodyMap["messages"]; !ok {
		t.Error("messages must be preserved in tunnel body")
	}
	if _, ok := bodyMap["store"]; !ok {
		t.Error("store field must be preserved in tunnel body")
	}
	if _, ok := bodyMap["parallel_tool_calls"]; !ok {
		t.Error("parallel_tool_calls field must be preserved in tunnel body")
	}
	if _, ok := bodyMap["codex_trace"]; !ok {
		t.Error("codex_trace field must be preserved in tunnel body")
	}
	if _, ok := bodyMap["custom_provider_options"]; !ok {
		t.Error("custom_provider_options field must be preserved in tunnel body")
	}
	if _, ok := bodyMap["nested"]; !ok {
		t.Error("nested unknown object must be preserved in tunnel body")
	}
	if _, ok := bodyMap["thinking_token_budget"]; !ok {
		t.Error("thinking_token_budget must be preserved in tunnel body")
	}

	// No IOP-internal metadata keys should leak into the pure passthrough body.
	for _, key := range []string{"estimated_input_tokens", "context_class"} {
		if _, ok := bodyMap[key]; ok {
			t.Errorf("IOP internal key %q must not be in tunnel body", key)
		}
	}

	if len(fake.reqsSnapshot()) != 0 {
		t.Errorf("normalized SubmitRun must not be called for tunnel path, got %d", len(fake.reqsSnapshot()))
	}
}

// TestChatCompletionsProviderPoolNormalizedIgnoresUnknownFields verifies that
// when the provider-pool dispatch selects the normalized path, the lenient
// ingress accepts unknown/provider-specific fields without rejecting the
// request, but the normalized RunRequest.Input only reflects standard supported
// fields. Unknown fields must never be invented or propagated into the adapter
// native request (SURFACE_FIELD).
func TestChatCompletionsProviderPoolNormalizedIgnoresUnknownFields(t *testing.T) {
	completeEvent := &iop.RunEvent{
		Type: "delta", Delta: "ok",
	}
	completeEvent2 := &iop.RunEvent{Type: "complete"}

	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
		poolRunFrames:    bufferedRunEvents(completeEvent, completeEvent2),
	}

	catalog := []config.ModelCatalogEntry{
		{ID: "ollama-pool-model", Providers: map[string]string{"prov-ollama": "qwen3.6:35b"}},
	}
	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog(catalog)

	reqBody := `{
		"model":"ollama-pool-model",
		"messages":[{"role":"user","content":"hello"}],
		"temperature":0.7,
		"store":true,
		"codex_trace":{"enabled":true},
		"custom_provider_options":{"timeout_ms":5000},
		"nested_unknown":{"deep":true},
		"reasoning_effort":"high"
	}`
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(reqBody))
	w := httptest.NewRecorder()
	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	// The lenient ingress must NOT reject the request due to unknown fields.
	runReqs := fake.reqsSnapshot()
	if len(runReqs) != 1 {
		t.Fatalf("expected 1 normalized SubmitRun, got %d", len(runReqs))
	}

	runReq := runReqs[0]
	if !runReq.ProviderPool {
		t.Error("normalized pool run must have ProviderPool=true")
	}

	// Standard supported fields must be present in Input.
	input := runReq.Input
	if _, ok := input["prompt"]; !ok {
		t.Error("prompt must be in normalized Input")
	}
	if _, ok := input["messages"]; !ok {
		t.Error("messages must be in normalized Input")
	}
	options, ok := input["options"].(map[string]any)
	if !ok {
		t.Fatalf("options must be in normalized Input, got %T", input["options"])
	}
	if options["temperature"] != 0.7 {
		t.Errorf("temperature must be forwarded to normalized options, got %+v", options["temperature"])
	}

	// Unknown/provider-specific fields must NOT be in the normalized Input.
	for _, key := range []string{"store", "codex_trace", "custom_provider_options", "nested_unknown"} {
		if _, ok := input[key]; ok {
			t.Errorf("unknown field %q must not be in normalized Input", key)
		}
		if _, ok := options[key]; ok {
			t.Errorf("unknown field %q must not be in normalized Input options", key)
		}
	}

	// The tunnel path must not be taken.
	tunnelReqs := fake.tunnelReqsSnapshot()
	if len(tunnelReqs) != 0 {
		t.Errorf("tunnel must not be dispatched for normalized path, got %d", len(tunnelReqs))
	}
}

// TestResponsesProviderPoolTunnelUnknownFieldsUsesPoolSelection asserts that
// when the selected provider is tunnel-passthrough (OpenAI-compatible),
// unknown fields (tools, parallel_tool_calls, store, custom field) are
// preserved verbatim in the tunnel body, and that the Run sent to
// SubmitProviderPool carries a non-empty ModelGroupKey.
func TestResponsesProviderPoolTunnelUnknownFieldsUsesPoolSelection(t *testing.T) {
	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
	}

	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog([]config.ModelCatalogEntry{
		{ID: "pool-model", Providers: map[string]string{"prov-tunnel": "served-tunnel"}},
	})

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"pool-model",
		"input":"hello world",
		"tools":[{"type":"function","function":{"name":"lookup"}}],
		"parallel_tool_calls":true,
		"store":false,
		"custom_field":"keep-me"
	}`))
	w := httptest.NewRecorder()

	srv.handleResponses(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	// SubmitProviderPool must have been called with a populated Run.
	lastRun := fake.poolLastRunSnapshot()
	if lastRun.ModelGroupKey == "" {
		t.Error("SubmitProviderPool was called with empty Run.ModelGroupKey")
	}

	// The tunnel body must preserve unknown fields verbatim.
	if len(fake.tunnelBodiesSnapshot()) != 1 {
		t.Fatalf("expected exactly one tunnel body, got %d", len(fake.tunnelBodiesSnapshot()))
	}
	body := fake.tunnelBodiesSnapshot()[0]
	if !strings.Contains(string(body), "\"tools\"") {
		t.Error("tunnel body must preserve tools field")
	}
	if !strings.Contains(string(body), "\"parallel_tool_calls\"") {
		t.Error("tunnel body must preserve parallel_tool_calls field")
	}
	if !strings.Contains(string(body), "\"store\"") {
		t.Error("tunnel body must preserve store field")
	}
	if !strings.Contains(string(body), "\"custom_field\"") {
		t.Error("tunnel body must preserve custom_field")
	}
}

// TestResponsesProviderPoolOllamaSelectionRejectsUnknownFieldsWithoutTunnel
// asserts that when the selected provider is normalized (Ollama/CLI), an
// unknown-field request returns HTTP 400 and no tunnel or run dispatch occurs.
func TestResponsesProviderPoolOllamaSelectionRejectsUnknownFieldsWithoutTunnel(t *testing.T) {
	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
	}

	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog([]config.ModelCatalogEntry{
		{ID: "ollama-model", Providers: map[string]string{"prov-ollama": "served-ollama"}},
	})

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"ollama-model",
		"input":"hello",
		"unknown_field":"reject-me"
	}`))
	w := httptest.NewRecorder()

	srv.handleResponses(w, req)

	if w.Code != http.StatusBadRequest {
		t.Fatalf("status: got %d body=%s, want 400", w.Code, w.Body.String())
	}

	// No tunnel or run dispatch should have occurred.
	if len(fake.tunnelReqsSnapshot()) != 0 {
		t.Error("normalized path must not dispatch tunnel for unknown fields")
	}
	if len(fake.reqsSnapshot()) != 0 {
		t.Error("normalized path must not dispatch run for unknown fields")
	}

	// Verify pool dispatch was made.
	if fake.poolSubmitCountSnapshot() != 1 {
		t.Fatalf("pool submit count: got %d, want 1", fake.poolSubmitCountSnapshot())
	}

	// Verify PrepareRun was called on the normalized path.
	prepareRunCalled := fake.poolPrepareRunCalledSnapshot()
	if !prepareRunCalled {
		t.Errorf("PrepareRun must be called on normalized path (poolSubmitCount=%d, poolLastRun.ModelGroupKey=%q)", fake.poolSubmitCountSnapshot(), fake.poolLastRunSnapshot().ModelGroupKey)
	}
}

// TestResponsesProviderPoolOllamaSelectionRejectsStreamingWithoutTunnel asserts
// that when the selected provider is normalized, stream:true returns HTTP 400
// and no tunnel dispatch occurs.
func TestResponsesProviderPoolOllamaSelectionRejectsStreamingWithoutTunnel(t *testing.T) {
	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathNormalized),
	}

	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog([]config.ModelCatalogEntry{
		{ID: "ollama-model", Providers: map[string]string{"prov-ollama": "served-ollama"}},
	})

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"ollama-model",
		"input":"hello",
		"stream":true
	}`))
	w := httptest.NewRecorder()

	srv.handleResponses(w, req)

	if w.Code != http.StatusBadRequest {
		t.Fatalf("status: got %d body=%s, want 400", w.Code, w.Body.String())
	}

	// No tunnel dispatch for normalized path with stream:true.
	if len(fake.tunnelReqsSnapshot()) != 0 {
		t.Error("normalized path must not dispatch tunnel for stream:true")
	}
	if len(fake.reqsSnapshot()) != 0 {
		t.Error("normalized path must not dispatch run for stream:true")
	}

	if !fake.poolPrepareRunCalledSnapshot() {
		t.Error("PrepareRun must be called on normalized path")
	}
}

// TestResponsesProviderPoolTunnelStreamingUsesPoolSelection asserts that when
// the selected provider is tunnel-passthrough, stream:true is relayed through
// SubmitProviderPool with the raw SSE body preserved.
func TestResponsesProviderPoolTunnelStreamingUsesPoolSelection(t *testing.T) {
	fake := &providerFakeRunService{
		poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel),
		tunnelFrames:     staticProviderTunnelFrames(`{"id":"resp-1","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"hello"}]}],"stream":true}\n`),
	}

	srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
	srv.SetModelCatalog([]config.ModelCatalogEntry{
		{ID: "pool-model", Providers: map[string]string{"prov-tunnel": "served-tunnel"}},
	})

	req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{
		"model":"pool-model",
		"input":"hello",
		"stream":true
	}`))
	w := httptest.NewRecorder()

	srv.handleResponses(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}

	// SubmitProviderPool must have been called.
	if fake.poolSubmitCountSnapshot() != 1 {
		t.Fatalf("SubmitProviderPool call count: got %d, want 1", fake.poolSubmitCountSnapshot())
	}

	// The tunnel body must preserve the raw SSE payload.
	if len(fake.tunnelBodiesSnapshot()) != 1 {
		t.Fatalf("expected one tunnel body, got %d", len(fake.tunnelBodiesSnapshot()))
	}
	body := fake.tunnelBodiesSnapshot()[0]
	if !strings.Contains(string(body), "\"stream\":true") {
		t.Error("tunnel body must preserve stream:true")
	}

	// PrepareRun must NOT be called on the tunnel path.
	if fake.poolPrepareRunCalledSnapshot() {
		t.Error("PrepareRun must NOT be called on tunnel path")
	}
}
