package service

import (
	"encoding/json"
	"fmt"
	"slices"
	"strings"
	"testing"

	"oci-portal/internal/aiwire"
)

func respReq(t *testing.T, raw string) aiwire.RespRequest {
	t.Helper()
	var req aiwire.RespRequest
	if err := json.Unmarshal([]byte(raw), &req); err != nil {
		t.Fatalf("unmarshal req: %v", err)
	}
	return req
}

func deref(p *int) int {
	if p == nil {
		return 0
	}
	return *p
}

func TestRespPassthroughValidate(t *testing.T) {
	prev := "resp_1"
	bg := true
	tests := []struct {
		name    string
		req     aiwire.RespRequest
		wantErr bool
	}{
		{"合法", aiwire.RespRequest{Model: "m", Tools: []aiwire.RespTool{{Type: "web_search"}}}, false},
		{"混用function", aiwire.RespRequest{Model: "m", Tools: []aiwire.RespTool{{Type: "web_search"}, {Type: "function", Name: "f"}}}, false},
		{"缺model", aiwire.RespRequest{Tools: []aiwire.RespTool{{Type: "web_search"}}}, true},
		{"服务端工具流式放行", aiwire.RespRequest{Model: "m", Stream: true, Tools: []aiwire.RespTool{{Type: "web_search"}}}, false},
		{"无工具流式放行", aiwire.RespRequest{Model: "m", Stream: true}, false},
		{"function工具流式放行", aiwire.RespRequest{Model: "m", Stream: true, Tools: []aiwire.RespTool{{Type: "function", Name: "f"}}}, false},
		{"code_interpreter放行", aiwire.RespRequest{Model: "m", Tools: []aiwire.RespTool{{Type: "code_interpreter"}}}, false},
		{"mcp流式放行", aiwire.RespRequest{Model: "m", Stream: true, Tools: []aiwire.RespTool{{Type: "mcp"}}}, false},
		{"有状态拒绝", aiwire.RespRequest{Model: "m", PreviousResponseID: prev}, true},
		{"background拒绝", aiwire.RespRequest{Model: "m", Background: &bg}, true},
		{"未知工具拒绝", aiwire.RespRequest{Model: "m", Tools: []aiwire.RespTool{{Type: "web_search"}, {Type: "file_search"}}}, true},
		{"namespace放行", aiwire.RespRequest{Model: "m", Tools: []aiwire.RespTool{{Type: "namespace", Name: "multi_agent_v1"}}}, false},
		{"custom放行", aiwire.RespRequest{Model: "m", Tools: []aiwire.RespTool{{Type: "custom", Name: "run_script"}}}, false},
		{"tool_search放行", aiwire.RespRequest{Model: "m", Tools: []aiwire.RespTool{{Type: "tool_search"}}}, false},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := RespPassthroughValidate(test.req)
			if (err != nil) != test.wantErr {
				t.Fatalf("err = %v, wantErr %v", err, test.wantErr)
			}
		})
	}
}

func TestRespPassthroughBody(t *testing.T) {
	raw := []byte(`{"model":"m","input":"hi","stream":true,"store":true,"max_output_tokens":128,"custom_field":{"a":1.5}}`)
	out, compat, err := RespPassthroughBody(raw)
	if err != nil {
		t.Fatalf("RespPassthroughBody: %v", err)
	}
	if len(compat.Dropped) != 0 || len(compat.Flattened) != 0 || len(compat.NsRefs) != 0 {
		t.Errorf("无 codex 工具不应有兼容改写: %+v", compat)
	}
	var body map[string]any
	if err := json.Unmarshal(out, &body); err != nil {
		t.Fatalf("unmarshal out: %v", err)
	}
	if body["store"] != false {
		t.Errorf("store 应强制 false, got %v", body["store"])
	}
	if body["stream"] != true {
		t.Errorf("stream 应原样保留, got %v", body["stream"])
	}
	if string(out) == "" || !strings.Contains(string(out), `"max_output_tokens":128`) {
		t.Errorf("数值字段应保真: %s", out)
	}
	if !strings.Contains(string(out), `"custom_field"`) {
		t.Errorf("未知字段应保留: %s", out)
	}
	if _, _, err := RespPassthroughBody([]byte("not-json")); err == nil {
		t.Error("非法 JSON 应报错")
	}
}

// TestRespQualifyNsName 断言限定名生成规则与 CLIProxyAPI 对齐。
func TestRespQualifyNsName(t *testing.T) {
	tests := []struct{ name, ns, tool, want string }{
		{"常规拼接", "multi_agent_v1", "spawn_agent", "multi_agent_v1__spawn_agent"},
		{"mcp子工具不加前缀", "mcp__sites", "mcp__sites__create_site", "mcp__sites__create_site"},
		{"ns以双下划线结尾", "codex_app__", "update", "codex_app__update"},
		{"已带前缀不重复", "ns1", "ns1__tool", "ns1__tool"},
		{"空ns原样", "", "tool", "tool"},
		{"空名返回空", "ns1", "", ""},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if got := respQualifyNsName(test.ns, test.tool); got != test.want {
				t.Fatalf("respQualifyNsName(%q,%q) = %q, want %q", test.ns, test.tool, got, test.want)
			}
		})
	}
}

// TestRespPassthroughBodyCompatTools 断言 codex 工具兼容改写:namespace 拍平为
// 限定名 function 并建映射,web_search 降权剥离,剔空连删 tool_choice。
func TestRespPassthroughBodyCompatTools(t *testing.T) {
	tests := []struct {
		name          string
		raw           string
		wantFlattened []string
		wantDropped   []string
		wantRefs      map[string]RespNsRef
		wantContains  []string
		wantAbsent    []string
	}{
		{
			"namespace拍平保留function",
			`{"model":"m","tools":[{"type":"function","name":"exec"},{"type":"namespace","name":"multi_agent_v1","tools":[{"type":"function","name":"spawn_agent","parameters":{"type":"object"}}]}]}`,
			[]string{"namespace:multi_agent_v1"},
			nil,
			map[string]RespNsRef{"multi_agent_v1__spawn_agent": {Namespace: "multi_agent_v1", Name: "spawn_agent"}},
			[]string{`"name":"multi_agent_v1__spawn_agent"`, `"name":"exec"`},
			[]string{`"type":"namespace"`},
		},
		{
			"mcp子工具保名并补parameters",
			`{"model":"m","tools":[{"type":"namespace","name":"mcp__sites","tools":[{"type":"function","name":"mcp__sites__create"}]}]}`,
			[]string{"namespace:mcp__sites"},
			nil,
			map[string]RespNsRef{"mcp__sites__create": {Namespace: "mcp__sites", Name: "mcp__sites__create"}},
			[]string{`"name":"mcp__sites__create"`, `"parameters":{"properties":{},"type":"object"}`},
			[]string{`"type":"namespace"`},
		},
		{
			"非function子工具剥离",
			`{"model":"m","tools":[{"type":"namespace","name":"ns1","tools":[{"type":"custom","name":"patch"},{"type":"function","name":"run"}]}]}`,
			[]string{"namespace:ns1"},
			[]string{"ns1.patch(type=custom)"},
			map[string]RespNsRef{"ns1__run": {Namespace: "ns1", Name: "run"}},
			[]string{`"name":"ns1__run"`},
			[]string{"patch"},
		},
		{
			"剔空连删tool_choice",
			`{"model":"m","tool_choice":"auto","parallel_tool_calls":false,"tools":[{"type":"web_search","external_web_access":false}]}`,
			nil,
			[]string{"web_search(external_web_access=false)"},
			nil,
			nil,
			[]string{`"tools"`, `"tool_choice"`, `"parallel_tool_calls"`},
		},
		{
			"web_search全访问仅删键",
			`{"model":"m","tools":[{"type":"web_search","external_web_access":true}]}`,
			nil,
			nil,
			nil,
			[]string{`"tools":[{"type":"web_search"}]`},
			[]string{"external_web_access"},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			out, compat, err := RespPassthroughBody([]byte(test.raw))
			if err != nil {
				t.Fatalf("RespPassthroughBody: %v", err)
			}
			if !slices.Equal(compat.Flattened, test.wantFlattened) {
				t.Errorf("Flattened = %v, want %v", compat.Flattened, test.wantFlattened)
			}
			if !slices.Equal(compat.Dropped, test.wantDropped) {
				t.Errorf("Dropped = %v, want %v", compat.Dropped, test.wantDropped)
			}
			for q, ref := range test.wantRefs {
				if compat.NsRefs[q] != ref {
					t.Errorf("NsRefs[%s] = %+v, want %+v", q, compat.NsRefs[q], ref)
				}
			}
			for _, s := range test.wantContains {
				if !strings.Contains(string(out), s) {
					t.Errorf("应包含 %s: %s", s, out)
				}
			}
			for _, s := range test.wantAbsent {
				if strings.Contains(string(out), s) {
					t.Errorf("不应包含 %s: %s", s, out)
				}
			}
		})
	}
}

// TestRespCompatHistoryAndToolChoice 断言多轮历史 function_call 与对象 tool_choice
// 的 namespace 字段被重限定并删除。
func TestRespCompatHistoryAndToolChoice(t *testing.T) {
	raw := `{"model":"m",
		"tool_choice":{"type":"function","name":"spawn_agent","namespace":"multi_agent_v1"},
		"input":[
			{"type":"function_call","name":"spawn_agent","namespace":"multi_agent_v1","call_id":"c1","arguments":"{}"},
			{"type":"function_call","name":"exec","call_id":"c2","arguments":"{}"},
			{"type":"function_call_output","call_id":"c1","output":"ok"}
		],
		"tools":[{"type":"namespace","name":"multi_agent_v1","tools":[{"type":"function","name":"spawn_agent","parameters":{}}]}]}`
	out, _, err := RespPassthroughBody([]byte(raw))
	if err != nil {
		t.Fatalf("RespPassthroughBody: %v", err)
	}
	s := string(out)
	if !strings.Contains(s, `"tool_choice":{"name":"multi_agent_v1__spawn_agent","type":"function"}`) {
		t.Errorf("tool_choice 应限定化: %s", s)
	}
	if strings.Contains(s, `"namespace":"multi_agent_v1"`) {
		t.Errorf("namespace 字段应全部删除: %s", s)
	}
	if !strings.Contains(s, `"name":"exec"`) {
		t.Errorf("无 namespace 的历史项应原样: %s", s)
	}
	if c := strings.Count(s, "multi_agent_v1__spawn_agent"); c != 3 {
		t.Errorf("限定名应出现 3 次(tools/input/tool_choice), got %d: %s", c, s)
	}
}

// TestRespRestoreNamespace 断言非流式响应把限定名还原为短名+namespace 双字段。
func TestRespRestoreNamespace(t *testing.T) {
	compat := RespCompat{NsRefs: map[string]RespNsRef{"multi_agent_v1__spawn_agent": {Namespace: "multi_agent_v1", Name: "spawn_agent"}}}
	payload := []byte(`{"id":"r1","output":[{"type":"function_call","name":"multi_agent_v1__spawn_agent","call_id":"c1","arguments":"{}"},{"type":"message","content":[]}],"usage":{"total_tokens":7}}`)
	out := RespRestoreToolCalls(payload, compat)
	s := string(out)
	if !strings.Contains(s, `"name":"spawn_agent"`) || !strings.Contains(s, `"namespace":"multi_agent_v1"`) {
		t.Errorf("应还原短名并补 namespace: %s", s)
	}
	if !strings.Contains(s, `"total_tokens":7`) {
		t.Errorf("其余字段应保真: %s", s)
	}
	for name, payload := range map[string][]byte{
		"未命中原样":  []byte(`{"output":[{"type":"function_call","name":"other","call_id":"c"}]}`),
		"非法JSON": []byte(`xx`),
	} {
		if got := RespRestoreToolCalls(payload, compat); string(got) != string(payload) {
			t.Errorf("%s: 应返回原字节, got %s", name, got)
		}
	}
	if got := RespRestoreToolCalls(payload, RespCompat{}); string(got) != string(payload) {
		t.Error("无需还原时应返回原字节")
	}
}

// TestRespRestoreNamespaceEvent 断言流式事件还原覆盖 item 与 response.output
// 两个位置,无关事件保持未改动。
func TestRespRestoreNamespaceEvent(t *testing.T) {
	compat := RespCompat{NsRefs: map[string]RespNsRef{"ns1__run": {Namespace: "ns1", Name: "run"}}}
	tests := []struct {
		name        string
		data        string
		wantChanged bool
		wantSub     string
	}{
		{"output_item.done", `{"type":"response.output_item.done","item":{"type":"function_call","name":"ns1__run","call_id":"c1"}}`, true, `"namespace":"ns1"`},
		{"completed快照", `{"type":"response.completed","response":{"output":[{"type":"function_call","name":"ns1__run"}],"usage":{"total_tokens":1}}}`, true, `"name":"run"`},
		{"文本增量不动", `{"type":"response.output_text.delta","delta":"hi"}`, false, ""},
		{"未命中不动", `{"type":"response.output_item.done","item":{"type":"function_call","name":"exec"}}`, false, ""},
		{"非法JSON不动", `not-json`, false, ""},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			out, changed := RespRestoreToolCallsEvent([]byte(test.data), compat)
			if changed != test.wantChanged {
				t.Fatalf("changed = %v, want %v", changed, test.wantChanged)
			}
			if !changed && string(out) != test.data {
				t.Fatalf("未改动应返回原字节: %s", out)
			}
			if test.wantSub != "" && !strings.Contains(string(out), test.wantSub) {
				t.Fatalf("应包含 %s: %s", test.wantSub, out)
			}
		})
	}
}

func TestRespPassthroughUsage(t *testing.T) {
	tests := []struct {
		name           string
		payload        string
		wantNil        bool
		prompt, cached int
	}{
		{"完整", `{"usage":{"input_tokens":9,"output_tokens":3,"total_tokens":12,"input_tokens_details":{"cached_tokens":5}}}`, false, 9, 5},
		{"无细分", `{"usage":{"input_tokens":9,"output_tokens":3,"total_tokens":12}}`, false, 9, 0},
		{"无usage", `{"id":"resp_1"}`, true, 0, 0},
		{"非法JSON", `xx`, true, 0, 0},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			usage := RespPassthroughUsage([]byte(test.payload))
			if (usage == nil) != test.wantNil {
				t.Fatalf("usage = %v, wantNil %v", usage, test.wantNil)
			}
			if usage == nil {
				return
			}
			if usage.PromptTokens != test.prompt || usage.CachedTokens() != test.cached {
				t.Fatalf("prompt=%d cached=%d, want %d/%d", usage.PromptTokens, usage.CachedTokens(), test.prompt, test.cached)
			}
		})
	}
}

// TestRespStreamCompletedUsage 断言流式 usage 只从 completed 事件提取。
func TestRespStreamCompletedUsage(t *testing.T) {
	completed := []byte(`{"type":"response.completed","response":{"usage":{"input_tokens":10,"output_tokens":5,"total_tokens":15,"input_tokens_details":{"cached_tokens":4}}}}`)
	if u := RespStreamCompletedUsage(completed); u == nil || u.PromptTokens != 10 || u.CompletionTokens != 5 ||
		u.PromptTokensDetails == nil || u.PromptTokensDetails.CachedTokens != 4 {
		t.Fatalf("completed 事件 usage 解析失败: %+v", u)
	}
	for _, data := range []string{
		`{"type":"response.output_text.delta","delta":"hi"}`,
		`{"type":"response.completed"}`,
		`not-json`,
	} {
		if u := RespStreamCompletedUsage([]byte(data)); u != nil {
			t.Fatalf("非 completed 或缺 response 应返回 nil: %s", data)
		}
	}
}

// TestRespGuardBytes 断言保险丝计量:instructions+tools 原始字节和,缺字段计 0,
// 解析失败返回 0 放行。
func TestRespGuardBytes(t *testing.T) {
	for _, tc := range []struct {
		name, body string
		want       int
	}{
		{"双字段", `{"instructions":"abcd","tools":[{"type":"web_search"}],"input":"xxxxxxxx"}`,
			len(`"abcd"`) + len(`[{"type":"web_search"}]`)},
		{"仅 instructions", `{"instructions":"abcd"}`, len(`"abcd"`)},
		{"均缺失 input 不计", `{"input":"xxxxxxxxxxxxxxxx"}`, 0},
		{"解析失败放行", `not-json`, 0},
	} {
		t.Run(tc.name, func(t *testing.T) {
			if got := RespGuardBytes([]byte(tc.body)); got != tc.want {
				t.Fatalf("RespGuardBytes() = %d, want %d", got, tc.want)
			}
		})
	}
}

// TestRespInjectGrokTools 断言 grok 搜索工具默认注入:仅 xai. 模型、开关可控、
// 已带同名工具不覆盖、注入清单正确。
func TestRespInjectGrokTools(t *testing.T) {
	for _, tc := range []struct {
		name, body, model string
		web, x            bool
		wantInjected      []string
		wantContains      []string
	}{
		{"非 xai 不注入", `{"tools":[]}`, "meta.llama-3.3", true, true, nil, nil},
		{"双开无 tools 字段注入两个", `{"model":"xai.grok-4.3"}`, "xai.grok-4.3", true, true,
			[]string{"web_search", "x_search"}, []string{`"web_search"`, `"x_search"`}},
		{"已带 web_search 只注入 x_search", `{"tools":[{"type":"web_search","filters":{"x":1}}]}`,
			"xai.grok-4.3", true, true, []string{"x_search"}, []string{`"filters"`}},
		{"开关全关不注入", `{"tools":[]}`, "xai.grok-4.3", false, false, nil, nil},
		{"仅开 x_search", `{"tools":[]}`, "xai.grok-4.3", false, true, []string{"x_search"}, nil},
		{"解析失败原样", `not-json`, "xai.grok-4.3", true, true, nil, nil},
	} {
		t.Run(tc.name, func(t *testing.T) {
			out, injected := RespInjectGrokTools([]byte(tc.body), tc.model, tc.web, tc.x)
			if fmt.Sprint(injected) != fmt.Sprint(tc.wantInjected) {
				t.Fatalf("injected = %v, want %v", injected, tc.wantInjected)
			}
			if len(injected) == 0 && string(out) != tc.body {
				t.Fatalf("未注入时应原样返回: %s", out)
			}
			for _, sub := range append(tc.wantContains, toolTypes(injected)...) {
				if !strings.Contains(string(out), sub) {
					t.Fatalf("输出缺少 %s: %s", sub, out)
				}
			}
		})
	}
}

// toolTypes 把注入清单转为输出应包含的片段断言。
func toolTypes(injected []string) []string {
	var out []string
	for _, typ := range injected {
		out = append(out, `{"type":"`+typ+`"}`)
	}
	return out
}

// TestRespDisableStream 断言流式升级回退把 stream 置 false 且其余字段保留。
func TestRespDisableStream(t *testing.T) {
	out, err := RespDisableStream([]byte(`{"model":"m","stream":true,"max_output_tokens":64}`))
	if err != nil {
		t.Fatalf("RespDisableStream: %v", err)
	}
	if !strings.Contains(string(out), `"stream":false`) || !strings.Contains(string(out), `"max_output_tokens":64`) {
		t.Fatalf("字段不符: %s", out)
	}
	if _, err := RespDisableStream([]byte("x")); err == nil {
		t.Error("非法 JSON 应报错")
	}
}

// TestRespSynthSSEEvents 断言合成事件序列:created(in_progress,空 output)→
// 逐项 output_item.done → completed(完整响应)。
func TestRespSynthSSEEvents(t *testing.T) {
	payload := []byte(`{"id":"r1","status":"completed","output":[{"type":"reasoning","summary":[]},{"type":"function_call","name":"run","call_id":"c1"}],"usage":{"total_tokens":9}}`)
	events, err := RespSynthSSEEvents(payload)
	if err != nil {
		t.Fatalf("RespSynthSSEEvents: %v", err)
	}
	if len(events) != 4 {
		t.Fatalf("事件数 = %d, want 4", len(events))
	}
	first := string(events[0])
	if !strings.Contains(first, `"type":"response.created"`) || !strings.Contains(first, `"status":"in_progress"`) || !strings.Contains(first, `"output":[]`) {
		t.Errorf("created 事件不符: %s", first)
	}
	if !strings.Contains(string(events[1]), `"type":"response.output_item.done"`) || !strings.Contains(string(events[1]), `"output_index":0`) {
		t.Errorf("item.done 事件不符: %s", events[1])
	}
	if !strings.Contains(string(events[2]), `"name":"run"`) {
		t.Errorf("第二项应为 function_call: %s", events[2])
	}
	last := string(events[3])
	if !strings.Contains(last, `"type":"response.completed"`) || !strings.Contains(last, `"status":"completed"`) ||
		!strings.Contains(last, `"total_tokens":9`) || !strings.Contains(last, `"name":"run"`) {
		t.Errorf("completed 事件不符: %s", last)
	}
	if _, err := RespSynthSSEEvents([]byte("x")); err == nil {
		t.Error("非法 JSON 应报错")
	}
}

// TestRespCompatCustomTools 断言 custom 工具转换:apply_patch 丢弃,其余转
// function 并补 input 包装 schema、记入 CustomNames;format 字段移除。
func TestRespCompatCustomTools(t *testing.T) {
	raw := `{"model":"m","tools":[
		{"type":"custom","name":"apply_patch","description":"edit files"},
		{"type":"custom","name":"run_script","description":"run it","format":{"type":"grammar"}},
		{"type":"function","name":"exec","parameters":{}}]}`
	out, compat, err := RespPassthroughBody([]byte(raw))
	if err != nil {
		t.Fatalf("RespPassthroughBody: %v", err)
	}
	if !slices.Equal(compat.Dropped, []string{"custom:apply_patch"}) {
		t.Errorf("Dropped = %v", compat.Dropped)
	}
	if !slices.Equal(compat.Converted, []string{"custom:run_script"}) {
		t.Errorf("Converted = %v", compat.Converted)
	}
	if _, ok := compat.CustomNames["run_script"]; !ok {
		t.Errorf("CustomNames 缺 run_script: %v", compat.CustomNames)
	}
	s := string(out)
	if strings.Contains(s, "apply_patch") || strings.Contains(s, `"type":"custom"`) || strings.Contains(s, "grammar") {
		t.Errorf("apply_patch/custom/format 应消失: %s", s)
	}
	if !strings.Contains(s, `"required":["input"]`) || !strings.Contains(s, `"input":{"type":"string"}`) {
		t.Errorf("run_script 应补 input 包装 schema: %s", s)
	}
}

// TestRespCompatCustomHistory 断言 input 历史的 custom_tool_call(_output) 转换
// 与 arguments 包装三分支。
func TestRespCompatCustomHistory(t *testing.T) {
	raw := `{"model":"m","input":[
		{"type":"custom_tool_call","call_id":"c1","name":"run_script","input":"plain text"},
		{"type":"custom_tool_call","call_id":"c2","name":"run_script","input":"{\"a\":1}"},
		{"type":"custom_tool_call","call_id":"c3","name":"run_script"},
		{"type":"custom_tool_call_output","call_id":"c1","output":"done"},
		{"type":"custom_tool_call_output","call_id":"c2","output":{"ok":true}}]}`
	out, _, err := RespPassthroughBody([]byte(raw))
	if err != nil {
		t.Fatalf("RespPassthroughBody: %v", err)
	}
	s := string(out)
	if strings.Contains(s, "custom_tool_call") {
		t.Fatalf("custom_tool_call 系应全部转换: %s", s)
	}
	for _, want := range []string{
		`"arguments":"{\"input\":\"plain text\"}"`,
		`"arguments":"{\"a\":1}"`,
		`"arguments":"{}"`,
		`"output":"done"`,
		`"output":"{\"ok\":true}"`,
	} {
		if !strings.Contains(s, want) {
			t.Errorf("应包含 %s: %s", want, s)
		}
	}
}

// TestRespRestoreCustomToolCalls 断言响应侧回转:命中 CustomNames 的
// function_call 变 custom_tool_call 且 input 解包;流式事件同样生效。
func TestRespRestoreCustomToolCalls(t *testing.T) {
	compat := RespCompat{CustomNames: map[string]struct{}{"run_script": {}}}
	payload := []byte(`{"output":[{"type":"function_call","name":"run_script","call_id":"c1","arguments":"{\"input\":\"echo hi\"}"},{"type":"function_call","name":"exec","call_id":"c2","arguments":"{}"}]}`)
	s := string(RespRestoreToolCalls(payload, compat))
	if !strings.Contains(s, `"type":"custom_tool_call"`) || !strings.Contains(s, `"input":"echo hi"`) {
		t.Errorf("应回转 custom_tool_call 并解包 input: %s", s)
	}
	if strings.Contains(s, `"arguments":"{\"input\":\"echo hi\"}"`) {
		t.Errorf("arguments 应删除: %s", s)
	}
	if !strings.Contains(s, `"name":"exec"`) || strings.Count(s, "custom_tool_call") != 1 {
		t.Errorf("非转换名不应动: %s", s)
	}
	ev := []byte(`{"type":"response.output_item.done","item":{"type":"function_call","name":"run_script","arguments":"not-json"}}`)
	got, changed := RespRestoreToolCallsEvent(ev, compat)
	if !changed || !strings.Contains(string(got), `"input":"not-json"`) {
		t.Errorf("非 JSON arguments 应整串作 input: %s", got)
	}
}

// TestRespUnwrapCustomInput 断言解包分支:input 键取值、非串取原文、其余整串。
func TestRespUnwrapCustomInput(t *testing.T) {
	tests := []struct {
		name string
		args any
		want string
	}{
		{"包装取input", `{"input":"hello"}`, "hello"},
		{"input非串取原文", `{"input":{"x":1}}`, `{"x":1}`},
		{"无input键整串", `{"cmd":"ls"}`, `{"cmd":"ls"}`},
		{"非JSON整串", "raw text", "raw text"},
		{"非串类型空串", 42, ""},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if got := respUnwrapCustomInput(test.args); got != test.want {
				t.Fatalf("respUnwrapCustomInput(%v) = %q, want %q", test.args, got, test.want)
			}
		})
	}
}

// TestRespCompatToolSearchDrop 断言 tool_search 剥离(拍平后语义冗余,上游不识别)。
func TestRespCompatToolSearchDrop(t *testing.T) {
	raw := `{"model":"m","tools":[{"type":"tool_search"},{"type":"function","name":"exec"}]}`
	out, compat, err := RespPassthroughBody([]byte(raw))
	if err != nil {
		t.Fatalf("RespPassthroughBody: %v", err)
	}
	if !slices.Equal(compat.Dropped, []string{"tool_search"}) {
		t.Errorf("Dropped = %v", compat.Dropped)
	}
	if strings.Contains(string(out), "tool_search") || !strings.Contains(string(out), `"name":"exec"`) {
		t.Errorf("tool_search 应剥离且 function 保留: %s", out)
	}
}
