package service

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

	"oci-portal/internal/aiwire"
)

// respRejectStateful 拒绝有状态特性(网关无状态)。
func respRejectStateful(req aiwire.RespRequest) error {
	if req.PreviousResponseID != "" {
		return fmt.Errorf("previous_response_id 不支持:网关不保存历史响应,请在 input 中自带完整上下文(store:false 模式)")
	}
	if len(req.Conversation) > 0 && string(req.Conversation) != "null" {
		return fmt.Errorf("conversation 不支持:网关不保存对话状态")
	}
	if req.Background != nil && *req.Background {
		return fmt.Errorf("background 模式不支持")
	}
	return nil
}

// RespPassthroughValidate 校验直通请求:模型必填,有状态特性不支持,工具类型
// 放行 function、Oracle 文档化的服务端工具(web_search / x_search /
// code_interpreter / mcp)与 codex 特有形态(namespace 工具分组、custom 自由
// 格式,上游不识别,转发前拍平/转换)。
func RespPassthroughValidate(req aiwire.RespRequest) error {
	if strings.TrimSpace(req.Model) == "" {
		return fmt.Errorf("model 不能为空")
	}
	if err := respRejectStateful(req); err != nil {
		return err
	}
	for _, t := range req.Tools {
		switch t.Type {
		case "function", "web_search", "x_search", "code_interpreter", "mcp", "namespace", "custom", "tool_search":
		default:
			return fmt.Errorf("不支持的工具类型 %q:服务端工具仅支持 web_search / x_search / code_interpreter / mcp", t.Type)
		}
	}
	return nil
}

// RespNsRef 一条拍平映射:限定名对应的原 namespace 与短名。codex 的回程路由
// 依赖 function_call 项上的 name+namespace 双字段,响应侧按此映射还原。
type RespNsRef struct {
	Namespace string
	Name      string
}

// RespCompat 是直通请求 codex 兼容改写的结果:Flattened/Converted/Dropped 供
// 调用方记日志;NsRefs / CustomNames 非空表示响应侧需做工具调用项还原。
type RespCompat struct {
	Flattened   []string
	Converted   []string
	Dropped     []string
	NsRefs      map[string]RespNsRef
	CustomNames map[string]struct{}
}

// NeedRestore 报告响应是否需要做工具调用项还原。
func (c RespCompat) NeedRestore() bool {
	return len(c.NsRefs) > 0 || len(c.CustomNames) > 0
}

// RespPassthroughBody 以原始请求体为基构造上游 body:强制 store:false(禁上游
// 存态),stream 原样保留(流式直通);用 json.Number 保真未知字段与数值。
// 同时做 codex 兼容改写(namespace 拍平、多轮历史与 tool_choice 重限定)。
func RespPassthroughBody(raw []byte) ([]byte, RespCompat, error) {
	dec := json.NewDecoder(strings.NewReader(string(raw)))
	dec.UseNumber()
	var body map[string]any
	if err := dec.Decode(&body); err != nil {
		return nil, RespCompat{}, fmt.Errorf("解析请求体: %w", err)
	}
	body["store"] = false
	compat := respCompatTools(body)
	respCompatInputCalls(body)
	respCompatToolChoice(body)
	out, err := json.Marshal(body)
	return out, compat, err
}

// respCompatTools 对 tools 做 codex 兼容改写(上游实测行为见任务档案):
// namespace 工具组上游 422,拆平为限定名 function 工具;custom(自由格式)上游
// 同样 422,apply_patch 丢弃(grok 系未训练该补丁格式,失败编辑不如 shell 回退),
// 其余转 function 包装;web_search 的 external_web_access 参数上游 400,false 是
// "仅缓存检索"降权模式,按不越权原则连工具剥离,true 等价默认行为仅删键;
// 剔空后删 tools,并连删 tool_choice 与 parallel_tool_calls(上游拒绝无 tools
// 带 tool_choice)。
func respCompatTools(body map[string]any) RespCompat {
	tools, ok := body["tools"].([]any)
	if !ok {
		return RespCompat{}
	}
	compat := RespCompat{NsRefs: map[string]RespNsRef{}, CustomNames: map[string]struct{}{}}
	kept := make([]any, 0, len(tools))
	for _, item := range tools {
		tool, _ := item.(map[string]any)
		switch {
		case tool != nil && tool["type"] == "namespace":
			ns, _ := tool["name"].(string)
			compat.Flattened = append(compat.Flattened, "namespace:"+ns)
			kept = append(kept, respFlattenNsTool(ns, tool, &compat)...)
			continue
		case tool != nil && tool["type"] == "custom":
			name, _ := tool["name"].(string)
			if name == "apply_patch" {
				compat.Dropped = append(compat.Dropped, "custom:apply_patch")
				continue
			}
			respConvertCustomTool(tool)
			if name != "" {
				compat.CustomNames[name] = struct{}{}
				compat.Converted = append(compat.Converted, "custom:"+name)
			}
			kept = append(kept, tool)
			continue
		}
		if desc, drop := respToolDrop(tool); drop {
			compat.Dropped = append(compat.Dropped, desc)
			continue
		}
		kept = append(kept, item)
	}
	if len(kept) == 0 {
		delete(body, "tools")
		delete(body, "tool_choice")
		delete(body, "parallel_tool_calls")
	} else {
		body["tools"] = kept
	}
	return compat
}

// respConvertCustomTool 把 custom(自由格式)工具改写为 function:补 input 包装
// schema(custom 工具本无 parameters),模型以 {"input": 文本} 形态调用,响应侧
// 按 CustomNames 回转;custom 专有的 format(语法约束)字段一并移除。
func respConvertCustomTool(tool map[string]any) {
	tool["type"] = "function"
	delete(tool, "format")
	if _, ok := tool["parameters"]; !ok {
		tool["parameters"] = map[string]any{
			"type":       "object",
			"properties": map[string]any{"input": map[string]any{"type": "string"}},
			"required":   []any{"input"},
		}
	}
}

// respFlattenNsTool 把 namespace 组内子工具上提为顶层工具:限定改名、缺
// parameters 补空 schema(上游必填),映射写入 NsRefs 供响应侧还原;非
// function 子工具(如 custom)上游同样不认,剥离并记录。
func respFlattenNsTool(ns string, tool map[string]any, compat *RespCompat) []any {
	children, _ := tool["tools"].([]any)
	out := make([]any, 0, len(children))
	for _, c := range children {
		child, _ := c.(map[string]any)
		if child == nil {
			continue
		}
		short, _ := child["name"].(string)
		if t, _ := child["type"].(string); t != "" && t != "function" {
			compat.Dropped = append(compat.Dropped, ns+"."+short+"(type="+t+")")
			continue
		}
		qualified := respQualifyNsName(ns, short)
		if qualified == "" {
			continue
		}
		child["name"] = qualified
		if _, ok := child["parameters"]; !ok {
			child["parameters"] = map[string]any{"type": "object", "properties": map[string]any{}}
		}
		compat.NsRefs[qualified] = RespNsRef{Namespace: ns, Name: short}
		out = append(out, child)
	}
	return out
}

// respQualifyNsName 生成拍平后的限定名,与响应侧还原互逆(对齐 CLIProxyAPI):
// mcp__ 开头的子工具名自带全局前缀不再限定;ns 以 __ 结尾直接拼接;已带前缀
// 不重复添加。
func respQualifyNsName(ns, name string) string {
	ns, name = strings.TrimSpace(ns), strings.TrimSpace(name)
	if ns == "" || name == "" || strings.HasPrefix(name, "mcp__") {
		return name
	}
	prefix := ns
	if !strings.HasSuffix(prefix, "__") {
		prefix += "__"
	}
	if strings.HasPrefix(name, prefix) {
		return name
	}
	return prefix + name
}

// respCompatInputCalls 改写多轮历史 input:function_call 的 namespace 字段重限定
// (上游不认识该字段);custom_tool_call / custom_tool_call_output 转换为上游
// 认识的 function_call(_output) 形态(上游 item 变体表不含 custom 系)。
func respCompatInputCalls(body map[string]any) {
	input, _ := body["input"].([]any)
	for _, it := range input {
		item, _ := it.(map[string]any)
		if item == nil {
			continue
		}
		switch item["type"] {
		case "function_call":
			respQualifyCallField(item)
		case "custom_tool_call":
			item["type"] = "function_call"
			item["arguments"] = respCustomCallArguments(item["input"])
			delete(item, "input")
		case "custom_tool_call_output":
			item["type"] = "function_call_output"
			item["output"] = respCustomCallOutput(item["output"])
		}
	}
}

// respCustomCallArguments 把 custom_tool_call 的 input 包装为 function_call 的
// arguments JSON 串(对齐 CLIProxyAPI):input 串本身是 JSON 对象则直用,普通
// 文本包一层 {"input": 文本};对象取序列化;缺失为 {}。与响应侧
// respUnwrapCustomInput 互逆,保证多轮往返无损。
func respCustomCallArguments(input any) string {
	switch v := input.(type) {
	case string:
		trimmed := strings.TrimSpace(v)
		var probe map[string]any
		if json.Unmarshal([]byte(trimmed), &probe) == nil && probe != nil {
			return trimmed
		}
		enc, err := json.Marshal(v)
		if err != nil {
			return "{}"
		}
		return `{"input":` + string(enc) + `}`
	case nil:
		return "{}"
	default:
		enc, err := json.Marshal(v)
		if err != nil {
			return "{}"
		}
		return string(enc)
	}
}

// respCustomCallOutput 归一 output 为字符串:非字符串时序列化保底。
func respCustomCallOutput(output any) any {
	if _, ok := output.(string); ok {
		return output
	}
	if output == nil {
		return ""
	}
	enc, err := json.Marshal(output)
	if err != nil {
		return ""
	}
	return string(enc)
}

// respCompatToolChoice 限定化对象形态的 tool_choice 及其 allowed_tools 列表
// (上游不认 tool_choice 上的 namespace 字段)。
func respCompatToolChoice(body map[string]any) {
	tc, _ := body["tool_choice"].(map[string]any)
	if tc == nil {
		return
	}
	if tc["type"] == "function" {
		respQualifyCallField(tc)
	}
	list, _ := tc["tools"].([]any)
	for _, it := range list {
		if sub, _ := it.(map[string]any); sub != nil && sub["type"] == "function" {
			respQualifyCallField(sub)
		}
	}
}

// respQualifyCallField 把一个带 name/namespace 的对象改写为限定名形态。
func respQualifyCallField(m map[string]any) {
	ns, _ := m["namespace"].(string)
	if strings.TrimSpace(ns) == "" {
		return
	}
	name, _ := m["name"].(string)
	if q := respQualifyNsName(ns, name); q != "" {
		m["name"] = q
	}
	delete(m, "namespace")
}

// respToolDrop 判定单个工具是否剥离。web_search 的 external_web_access 参数
// 上游 400:false 表示 OpenAI 的"仅缓存检索"降权模式,上游无对应能力,按不
// 越权原则整个工具剥离;true 等价上游默认行为,仅删键放行。tool_search(codex
// 的工具目录搜索)上游不识别,且 namespace 已全量拍平上送、搜索语义冗余,剥离。
func respToolDrop(tool map[string]any) (string, bool) {
	if tool == nil {
		return "", false
	}
	switch tool["type"] {
	case "tool_search":
		return "tool_search", true
	case "web_search":
		access, has := tool["external_web_access"]
		if !has {
			return "", false
		}
		delete(tool, "external_web_access")
		if access == false {
			return "web_search(external_web_access=false)", true
		}
	}
	return "", false
}

// RespGuardBytes 返回请求体中 instructions 与 tools 两字段的原始字节数之和,
// 流式保险丝据此判定。上游对二者合计 >≈64.5KB 的流式请求会在推理阶段静默断流
// (纯 EOF,input 正文不计入;2026-07-16 实测仍存在),阈值由设置页 AI Tab 配置。
// 解析失败返回 0(放行,交由上游正常报错)。
func RespGuardBytes(body []byte) int {
	var probe struct {
		Instructions json.RawMessage `json:"instructions"`
		Tools        json.RawMessage `json:"tools"`
	}
	if err := json.Unmarshal(body, &probe); err != nil {
		return 0
	}
	return len(probe.Instructions) + len(probe.Tools)
}

// RespInjectGrokTools 为 xai. 前缀模型默认注入服务端搜索工具:开关开启且请求
// tools 中不存在同名工具时追加 {"type":"web_search"} / {"type":"x_search"};
// 已存在(含任意参数形态)不覆盖。返回改写后 body 与注入清单(观测日志用);
// 模型不匹配、两开关全关或解析失败时原样返回。
func RespInjectGrokTools(body []byte, model string, web, x bool) ([]byte, []string) {
	if !strings.HasPrefix(model, "xai.") || (!web && !x) {
		return body, nil
	}
	dec := json.NewDecoder(strings.NewReader(string(body)))
	dec.UseNumber()
	var m map[string]any
	if err := dec.Decode(&m); err != nil {
		return body, nil
	}
	tools, _ := m["tools"].([]any)
	missing := map[string]bool{"web_search": web, "x_search": x}
	for _, t := range tools {
		tool, ok := t.(map[string]any)
		if !ok {
			continue
		}
		if typ, _ := tool["type"].(string); missing[typ] {
			missing[typ] = false
		}
	}
	var injected []string
	for _, typ := range []string{"web_search", "x_search"} {
		if missing[typ] {
			tools = append(tools, map[string]any{"type": typ})
			injected = append(injected, typ)
		}
	}
	if len(injected) == 0 {
		return body, nil
	}
	m["tools"] = tools
	out, err := json.Marshal(m)
	if err != nil {
		return body, nil
	}
	return out, injected
}

// RespDisableStream 把请求体的 stream 改为 false(流式升级回退用),其余字段
// 原样保留。
func RespDisableStream(body []byte) ([]byte, error) {
	dec := json.NewDecoder(strings.NewReader(string(body)))
	dec.UseNumber()
	var m map[string]any
	if err := dec.Decode(&m); err != nil {
		return nil, fmt.Errorf("解析请求体: %w", err)
	}
	m["stream"] = false
	return json.Marshal(m)
}

// RespSynthSSEEvents 把一份非流式响应合成为最小 SSE 事件序列:created →
// 每个输出项一条 output_item.done → completed。流式升级回退用,客户端拿到
// 完整事件语义但无增量;输出项与 usage 原样承载。
func RespSynthSSEEvents(payload []byte) ([][]byte, error) {
	dec := json.NewDecoder(strings.NewReader(string(payload)))
	dec.UseNumber()
	var resp map[string]any
	if err := dec.Decode(&resp); err != nil {
		return nil, fmt.Errorf("解析上游响应: %w", err)
	}
	output, _ := resp["output"].([]any)
	status, hadStatus := resp["status"]
	resp["status"], resp["output"] = "in_progress", []any{}
	created, err := json.Marshal(map[string]any{"type": "response.created", "response": resp})
	if err != nil {
		return nil, err
	}
	if hadStatus {
		resp["status"] = status
	} else {
		delete(resp, "status")
	}
	resp["output"] = output
	events := [][]byte{created}
	for i, item := range output {
		ev, err := json.Marshal(map[string]any{"type": "response.output_item.done", "output_index": i, "item": item})
		if err != nil {
			return nil, err
		}
		events = append(events, ev)
	}
	completed, err := json.Marshal(map[string]any{"type": "response.completed", "response": resp})
	if err != nil {
		return nil, err
	}
	return append(events, completed), nil
}

// RespRestoreToolCalls 非流式响应还原:output 数组中命中 NsRefs 的 function_call
// 把限定名还原为短名并补回 namespace 字段(codex 以该双字段路由);命中
// CustomNames 的回转 custom_tool_call(codex 期望 input 字段形态)。无需还原或
// 解析失败时返回原字节,不破坏直通。
func RespRestoreToolCalls(payload []byte, compat RespCompat) []byte {
	if !compat.NeedRestore() {
		return payload
	}
	dec := json.NewDecoder(strings.NewReader(string(payload)))
	dec.UseNumber()
	var body map[string]any
	if dec.Decode(&body) != nil {
		return payload
	}
	if !respRestoreOutput(body["output"], compat) {
		return payload
	}
	out, err := json.Marshal(body)
	if err != nil {
		return payload
	}
	return out
}

// RespRestoreToolCallsEvent 流式单事件还原:处理事件顶层 item(output_item.*)
// 与 response.output(created/completed 等快照)。返回还原后数据与是否改动;
// 未改动时调用方转发原始行,保持字节级直通。
func RespRestoreToolCallsEvent(data []byte, compat RespCompat) ([]byte, bool) {
	if !compat.NeedRestore() {
		return data, false
	}
	dec := json.NewDecoder(strings.NewReader(string(data)))
	dec.UseNumber()
	var ev map[string]any
	if dec.Decode(&ev) != nil {
		return data, false
	}
	changed := respRestoreCallItem(ev["item"], compat)
	if resp, _ := ev["response"].(map[string]any); resp != nil {
		changed = respRestoreOutput(resp["output"], compat) || changed
	}
	if !changed {
		return data, false
	}
	out, err := json.Marshal(ev)
	if err != nil {
		return data, false
	}
	return out, true
}

// respRestoreOutput 还原一个 output 数组,返回是否有改动。
func respRestoreOutput(v any, compat RespCompat) bool {
	items, _ := v.([]any)
	changed := false
	for _, it := range items {
		if respRestoreCallItem(it, compat) {
			changed = true
		}
	}
	return changed
}

// respRestoreCallItem 还原单个 function_call 项,返回是否改动。namespace 还原与
// custom 回转互斥:custom 工具不参与 namespace 拍平。
func respRestoreCallItem(v any, compat RespCompat) bool {
	item, _ := v.(map[string]any)
	if item == nil || item["type"] != "function_call" {
		return false
	}
	name, _ := item["name"].(string)
	if ref, ok := compat.NsRefs[name]; ok {
		item["name"] = ref.Name
		item["namespace"] = ref.Namespace
		return true
	}
	if _, ok := compat.CustomNames[name]; ok {
		item["type"] = "custom_tool_call"
		item["input"] = respUnwrapCustomInput(item["arguments"])
		delete(item, "arguments")
		return true
	}
	return false
}

// respUnwrapCustomInput 解包 arguments:形如 {"input": 串} 取其 input(非串取该
// 值原文),其余情况整串返回。与请求侧 respCustomCallArguments 互逆。
func respUnwrapCustomInput(arguments any) string {
	s, _ := arguments.(string)
	var probe map[string]json.RawMessage
	if json.Unmarshal([]byte(s), &probe) == nil {
		if raw, ok := probe["input"]; ok {
			var str string
			if json.Unmarshal(raw, &str) == nil {
				return str
			}
			return string(raw)
		}
	}
	return s
}

// RespPassthroughUsage 从直通响应提取用量;缺失时返回 nil(日志记零)。
func RespPassthroughUsage(payload []byte) *aiwire.Usage {
	var root struct {
		Usage *aiwire.RespUsage `json:"usage"`
	}
	if json.Unmarshal(payload, &root) != nil {
		return nil
	}
	return usageFromResp(root.Usage)
}

// usageFromResp 换算 Responses usage 为 OpenAI 口径(缓存命中透传);nil 原样返回。
func usageFromResp(u *aiwire.RespUsage) *aiwire.Usage {
	if u == nil {
		return nil
	}
	usage := &aiwire.Usage{PromptTokens: u.InputTokens,
		CompletionTokens: u.OutputTokens, TotalTokens: u.TotalTokens}
	if cached := u.InputTokensDetails.CachedTokens; cached > 0 {
		usage.PromptTokensDetails = &aiwire.PromptTokensDetails{CachedTokens: cached}
	}
	return usage
}

// RespStreamCompletedUsage 从一行 SSE data JSON 中提取 response.completed 事件的
// usage;非 completed 事件或解析失败返回 nil。流式直通逐行喂入,最后一次非 nil 生效。
func RespStreamCompletedUsage(data []byte) *aiwire.Usage {
	var ev struct {
		Type     string          `json:"type"`
		Response json.RawMessage `json:"response"`
	}
	if json.Unmarshal(data, &ev) != nil || ev.Type != "response.completed" || len(ev.Response) == 0 {
		return nil
	}
	return RespPassthroughUsage(ev.Response)
}

// RespStreamErrorMsg 从一行 SSE data JSON 中提取 error / response.failed 事件的
// 错误消息;非错误事件返回空串。流式直通据此把上游错误写入调用日志。
func RespStreamErrorMsg(data []byte) string {
	var ev respStreamEvent
	if json.Unmarshal(data, &ev) != nil {
		return ""
	}
	switch ev.Type {
	case "error":
		if ev.Message != "" {
			return ev.Message
		}
		return "上游返回错误事件 error"
	case "response.failed":
		if m := respErrorMsg(ev.Response); m != "" {
			return m
		}
		return "上游返回错误事件 response.failed"
	}
	return ""
}
