package service

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

	"oci-portal/internal/aiwire"
)

func mustAnthReq(t *testing.T, raw string) aiwire.MessagesRequest {
	t.Helper()
	var req aiwire.MessagesRequest
	if err := json.Unmarshal([]byte(raw), &req); err != nil {
		t.Fatalf("解析请求: %v", err)
	}
	return req
}

// TestAnthropicToResponsesBody 断言 system/消息/工具/effort 的直通装配与 store 强制。
func TestAnthropicToResponsesBody(t *testing.T) {
	req := mustAnthReq(t, `{
		"model": "xai.grok-4.3", "max_tokens": 128, "system": "你是助手", "stream": true,
		"temperature": 0.5,
		"messages": [
			{"role": "user", "content": "东京天气?"},
			{"role": "assistant", "content": [
				{"type": "text", "text": "查询中"},
				{"type": "tool_use", "id": "t1", "name": "get_weather", "input": {"city": "东京"}}
			]},
			{"role": "user", "content": [
				{"type": "tool_result", "tool_use_id": "t1", "content": "晴 25 度"},
				{"type": "text", "text": "继续"}
			]}
		],
		"tools": [{"name": "get_weather", "description": "查天气", "input_schema": {"type": "object"}}],
		"tool_choice": {"type": "any"},
		"output_config": {"effort": "HIGH"}
	}`)
	payload, err := AnthropicToResponsesBody(req)
	if err != nil {
		t.Fatalf("AnthropicToResponsesBody: %v", err)
	}
	var body map[string]any
	if err := json.Unmarshal(payload, &body); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if body["model"] != "xai.grok-4.3" || body["max_output_tokens"] != float64(128) ||
		body["instructions"] != "你是助手" || body["store"] != false || body["stream"] != true {
		t.Fatalf("顶层字段装配错误: %v", body)
	}
	if body["tool_choice"] != "required" {
		t.Fatalf("tool_choice = %v, want required", body["tool_choice"])
	}
	reasoning, _ := body["reasoning"].(map[string]any)
	if reasoning["effort"] != "high" {
		t.Fatalf("effort = %v, want high(小写透传)", reasoning)
	}
	input, _ := body["input"].([]any)
	// user 消息、assistant 文本消息、function_call、function_call_output、末条 user 文本
	if len(input) != 5 {
		t.Fatalf("input 项数 = %d, want 5: %s", len(input), payload)
	}
	kinds := make([]string, 0, len(input))
	for _, it := range input {
		m := it.(map[string]any)
		if ty, ok := m["type"].(string); ok {
			kinds = append(kinds, ty)
		} else {
			kinds = append(kinds, "message/"+m["role"].(string))
		}
	}
	want := "message/user,message/assistant,function_call,function_call_output,message/user"
	if got := strings.Join(kinds, ","); got != want {
		t.Fatalf("input 顺序 = %s, want %s", got, want)
	}
	if !strings.Contains(string(payload), `"output_text"`) {
		t.Fatalf("assistant 历史文本应为 output_text 部件: %s", payload)
	}
}

// TestAnthropicToResponsesBodyRejects 断言不支持的内容块拒绝与图片装配。
func TestAnthropicToResponsesBodyRejects(t *testing.T) {
	bad := mustAnthReq(t, `{"model":"m","max_tokens":8,"messages":[
		{"role":"user","content":[{"type":"document","source":{}}]}]}`)
	if _, err := AnthropicToResponsesBody(bad); err == nil {
		t.Fatal("document 块应拒绝")
	}
	img := mustAnthReq(t, `{"model":"m","max_tokens":8,"messages":[
		{"role":"user","content":[{"type":"image","source":{"type":"base64","media_type":"image/png","data":"QUJD"}}]}]}`)
	payload, err := AnthropicToResponsesBody(img)
	if err != nil || !strings.Contains(string(payload), "data:image/png;base64,QUJD") {
		t.Fatalf("图片应转 data URI: %v %s", err, payload)
	}
}

// TestResponsesToAnthropic 断言输出块、stop_reason 与 usage 的回转。
func TestResponsesToAnthropic(t *testing.T) {
	payload := []byte(`{"model":"xai.grok-4.3","status":"completed","output":[
		{"type":"reasoning","summary":[]},
		{"type":"message","content":[{"type":"output_text","text":"你好"}]},
		{"type":"function_call","call_id":"c1","name":"get_weather","arguments":"{\"city\":\"东京\"}"}],
		"usage":{"input_tokens":10,"output_tokens":5,"total_tokens":15,"input_tokens_details":{"cached_tokens":4}}}`)
	out, err := ResponsesToAnthropic(payload, "msg_1")
	if err != nil {
		t.Fatalf("ResponsesToAnthropic: %v", err)
	}
	if len(out.Content) != 2 || out.Content[0].Type != "text" || out.Content[0].Text != "你好" {
		t.Fatalf("content 装配错误: %+v", out.Content)
	}
	if out.Content[1].Type != "tool_use" || out.Content[1].ID != "c1" || string(out.Content[1].Input) != `{"city":"东京"}` {
		t.Fatalf("tool_use 装配错误: %+v", out.Content[1])
	}
	if out.StopReason != "tool_use" {
		t.Fatalf("stop_reason = %s, want tool_use", out.StopReason)
	}
	if out.Usage.InputTokens != 10 || out.Usage.OutputTokens != 5 || out.Usage.CacheReadInputTokens != 4 {
		t.Fatalf("usage = %+v", out.Usage)
	}

	trunc := []byte(`{"model":"m","status":"incomplete","incomplete_details":{"reason":"max_output_tokens"},
		"output":[{"type":"message","content":[{"type":"output_text","text":"半"}]}]}`)
	out2, err := ResponsesToAnthropic(trunc, "msg_2")
	if err != nil || out2.StopReason != "max_tokens" {
		t.Fatalf("截断 stop_reason = %v, %v", out2, err)
	}
}

func bridgeEventTypes(events []AnthEvent) string {
	kinds := make([]string, 0, len(events))
	for _, ev := range events {
		kinds = append(kinds, ev.Event)
	}
	return strings.Join(kinds, ",")
}

// TestAnthRespBridge 断言流桥:文本增量、工具调用切块、reasoning 丢弃与收尾事件。
func TestAnthRespBridge(t *testing.T) {
	st := NewAnthRespBridge("msg_1", "m1")
	var events []AnthEvent
	feed := func(lines ...string) {
		for _, l := range lines {
			events = append(events, st.Feed([]byte(l))...)
		}
	}
	feed(`{"type":"response.created","response":{"model":"m1"}}`,
		`{"type":"response.reasoning_text.delta","delta":"思考中"}`,
		`{"type":"response.output_text.delta","delta":"你"}`,
		`{"type":"response.output_text.delta","delta":"好"}`,
		`{"type":"response.output_item.added","item":{"type":"function_call","call_id":"c1","name":"f"}}`,
		`{"type":"response.function_call_arguments.delta","delta":"{\"a\":1}"}`,
		`{"type":"response.completed","response":{"status":"completed","usage":{"input_tokens":8,"output_tokens":4}}}`)
	events = append(events, st.Finish()...)

	got := bridgeEventTypes(events)
	want := "message_start,content_block_start,content_block_delta,content_block_delta," +
		"content_block_stop,content_block_start,content_block_delta,content_block_stop," +
		"message_delta,message_stop"
	if got != want {
		t.Fatalf("事件序列:\n got %s\nwant %s", got, want)
	}
	if st.Usage().InputTokens != 8 || st.Usage().OutputTokens != 4 {
		t.Fatalf("usage = %+v", st.Usage())
	}
}

// TestAnthRespBridgeFailure 断言异常流的错误透传:上游 error / response.failed
// 事件转 Anthropic error 事件,提前 EOF(未见终态)不再伪装正常结束。
func TestAnthRespBridgeFailure(t *testing.T) {
	cases := []struct {
		name      string
		lines     []string
		wantKinds string
		wantErr   string
	}{
		{
			name:      "上游 error 事件透传",
			lines:     []string{`{"type":"error","message":"model overloaded"}`},
			wantKinds: "error",
			wantErr:   "model overloaded",
		},
		{
			name: "response.failed 提取错误消息",
			lines: []string{
				`{"type":"response.output_text.delta","delta":"你"}`,
				`{"type":"response.failed","response":{"status":"failed","error":{"message":"content filtered"}}}`,
			},
			wantKinds: "message_start,content_block_start,content_block_delta,error",
			wantErr:   "content filtered",
		},
		{
			name:      "空流提前终止",
			lines:     nil,
			wantKinds: "error",
			wantErr:   "上游流提前终止,未返回终态事件",
		},
		{
			name: "输出中途 EOF 无终态",
			lines: []string{
				`{"type":"response.output_text.delta","delta":"你"}`,
			},
			wantKinds: "message_start,content_block_start,content_block_delta,error",
			wantErr:   "上游流提前终止,未返回终态事件",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			st := NewAnthRespBridge("msg_1", "m1")
			var events []AnthEvent
			for _, l := range tc.lines {
				events = append(events, st.Feed([]byte(l))...)
			}
			events = append(events, st.Finish()...)
			if got := bridgeEventTypes(events); got != tc.wantKinds {
				t.Fatalf("事件序列 = %s, want %s", got, tc.wantKinds)
			}
			if st.Err() != tc.wantErr {
				t.Fatalf("Err() = %q, want %q", st.Err(), tc.wantErr)
			}
		})
	}
}

// TestRespStreamErrorMsg 断言直通流错误事件消息提取。
func TestRespStreamErrorMsg(t *testing.T) {
	cases := []struct {
		name string
		data string
		want string
	}{
		{name: "error 事件", data: `{"type":"error","message":"boom"}`, want: "boom"},
		{name: "error 无消息用占位", data: `{"type":"error"}`, want: "上游返回错误事件 error"},
		{name: "failed 事件", data: `{"type":"response.failed","response":{"error":{"message":"bad"}}}`, want: "bad"},
		{name: "正常事件返回空", data: `{"type":"response.completed","response":{}}`, want: ""},
		{name: "非 JSON 返回空", data: `<html>`, want: ""},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := RespStreamErrorMsg([]byte(tc.data)); got != tc.want {
				t.Fatalf("RespStreamErrorMsg = %q, want %q", got, tc.want)
			}
		})
	}
}

// TestAnthMessageEvents 断言非流式降级结果展开的事件序列与 usage。
func TestAnthMessageEvents(t *testing.T) {
	msg := &aiwire.MessagesResponse{
		ID: "msg_1", Type: "message", Role: "assistant", Model: "m1",
		Content: []aiwire.AnthBlock{
			{Type: "text", Text: "好"},
			{Type: "tool_use", ID: "c1", Name: "f", Input: json.RawMessage(`{"a":1}`)},
		},
		StopReason: "tool_use",
		Usage:      aiwire.AnthUsage{InputTokens: 9, OutputTokens: 3, CacheReadInputTokens: 5},
	}
	events := AnthMessageEvents(msg)
	want := "message_start,content_block_start,content_block_delta,content_block_stop," +
		"content_block_start,content_block_delta,content_block_stop,message_delta,message_stop"
	if got := bridgeEventTypes(events); got != want {
		t.Fatalf("事件序列:\n got %s\nwant %s", got, want)
	}
	delta := events[len(events)-2].Data.(map[string]any)
	usage := delta["usage"].(map[string]int)
	if usage["input_tokens"] != 9 || usage["output_tokens"] != 3 || usage["cache_read_input_tokens"] != 5 {
		t.Fatalf("usage = %+v", usage)
	}
}
