package openai

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

	"iop/packages/go/config"
	iop "iop/proto/gen/iop"
)

func TestChatCompletionsMapsMaxCompletionTokens(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"metadata":{},
		"model":"from-request",
		"messages":[{"role":"user","content":"hi"}],
		"max_completion_tokens":7
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	options, ok := fake.req.Input["options"].(map[string]any)
	if !ok {
		t.Fatalf("options not passed: %+v", fake.req.Input)
	}
	if options["max_tokens"].(int) != 7 {
		t.Fatalf("max_completion_tokens not mapped to provider max_tokens: %+v", options)
	}
}

func TestChatCompletionsStreamsSSE(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "hi"}
	fake.events <- &iop.RunEvent{Type: "complete", Metadata: map[string]string{"finish_reason": "length"}}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"content":"hi"`) || !strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected SSE body:\n%s", body)
	}
	if !strings.Contains(body, `"finish_reason":"length"`) {
		t.Fatalf("streaming finish_reason did not preserve provider value:\n%s", body)
	}
}

func TestChatCompletionsStreamSynthesizesToolCallsFromClineTextBlock(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "먼저 현재 git 상태를 확인하겠습니다.\n\n"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "<tool_call>\n<function=run_commands>\n<parameter=commands>\n[\"cd /config/workspace/iop && git status\"]\n</parameter>\n</function>\n</tool_call>"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "cli", Target: "codex"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"status"}],
		"tools":[{"type":"function","function":{"name":"run_commands"}}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"tool_calls"`) || !strings.Contains(body, `"finish_reason":"tool_calls"`) {
		t.Fatalf("stream did not synthesize tool_calls:\n%s", body)
	}
	if strings.Contains(body, "<tool_call>") || strings.Contains(body, `\u003ctool_call`) {
		t.Fatalf("stream leaked raw tool_call block:\n%s", body)
	}
	if !strings.Contains(body, `"index":0`) {
		t.Fatalf("stream tool_call delta should include index:\n%s", body)
	}
	if !strings.Contains(body, `"content":"먼저 현재 git 상태를 확인하겠습니다."`) {
		t.Fatalf("stream should preserve text before tool call:\n%s", body)
	}
}

func TestChatCompletionsStreamWithToolsBuffersTextUntilComplete(t *testing.T) {
	fake := &fakeRunService{events: bufferedRunEvents(
		&iop.RunEvent{Type: "delta", Delta: "streaming before complete"},
		&iop.RunEvent{Type: "complete"},
	)}
	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"metadata":{},
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"explain sudo"}],
		"tools":[{"type":"function","function":{"name":"run_commands"}}],
		"tool_choice":"auto"
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	reqs := fake.reqsSnapshot()
	if len(reqs) != 1 {
		t.Fatalf("submit attempts: got %d want 1", len(reqs))
	}
	if reqs[0].Metadata[runtimeMetadataToolValidationAttempt] != "1" {
		t.Fatalf("tool stream should enable buffered validation metadata: %+v", reqs[0].Metadata)
	}
	body := w.Body.String()
	if !strings.Contains(body, `"content":"streaming before complete"`) ||
		!strings.Contains(body, `"finish_reason":"stop"`) ||
		!strings.Contains(body, "data: [DONE]") {
		t.Fatalf("buffered stream did not emit final content:\n%s", body)
	}
}

func TestChatCompletionsStreamSynthesizesToolCallsFromClineTemplateCall(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "먼저 현재 git 상태를 확인하겠습니다.\n\n"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "{{run_commands(commands=[{'command': 'git status', 'description': '현재 git 상태 확인', 'runInTerminal': True}]})}}"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "cli", Target: "codex"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"status"}],
		"tools":[{"type":"function","function":{"name":"run_commands"}}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"tool_calls"`) || !strings.Contains(body, `"finish_reason":"tool_calls"`) {
		t.Fatalf("stream did not synthesize tool_calls:\n%s", body)
	}
	if strings.Contains(body, "{{run_commands") {
		t.Fatalf("stream leaked raw template tool call:\n%s", body)
	}
	if !strings.Contains(body, `"index":0`) {
		t.Fatalf("stream tool_call delta should include index:\n%s", body)
	}
	if !strings.Contains(body, `\"commands\":[{\"args\":[\"status\"],\"command\":\"git\"}]`) &&
		!strings.Contains(body, `\"commands\":[{\"command\":\"git\",\"args\":[\"status\"]}]`) {
		t.Fatalf("stream tool_call arguments should include command:\n%s", body)
	}
	if strings.Contains(body, "runInTerminal") || strings.Contains(body, "description") {
		t.Fatalf("stream tool_call arguments should strip execution metadata:\n%s", body)
	}
	if !strings.Contains(body, `"content":"먼저 현재 git 상태를 확인하겠습니다."`) {
		t.Fatalf("stream should preserve text before tool call:\n%s", body)
	}
}

func TestChatCompletionsStreamPassesThroughNativeToolCallsFromProviderRoute(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "checking"}
	fake.events <- &iop.RunEvent{
		Type: "complete",
		Metadata: map[string]string{
			"finish_reason":                "tool_calls",
			runtimeMetadataOpenAIToolCalls: `[{"id":"call_1","type":"function","function":{"name":"run_commands","arguments":"{\"commands\":[\"git status\"]}"}}]`,
		},
	}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"metadata":{},
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"status"}],
		"tools":[{"type":"function","function":{"name":"run_commands"}}],
		"tool_choice":"auto"
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"tool_calls"`) || !strings.Contains(body, `"call_1"`) {
		t.Fatalf("stream did not pass through native tool_calls:\n%s", body)
	}
	if !strings.Contains(body, `"finish_reason":"tool_calls"`) {
		t.Fatalf("stream finish_reason:\n%s", body)
	}
}

func TestChatCompletionsStreamDropsRawTextWhenNativeToolCallsArrive(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: `<tool_call><function=run_commands><parameter=commands>["git status"]</parameter></function></tool_call>`}
	fake.events <- &iop.RunEvent{
		Type: "complete",
		Metadata: map[string]string{
			"finish_reason":                "tool_calls",
			runtimeMetadataOpenAIToolCalls: `[{"id":"call_1","type":"function","function":{"name":"run_commands","arguments":"{\"commands\":[\"git status\"]}"}}]`,
		},
	}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"metadata":{},
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"status"}],
		"tools":[{"type":"function","function":{"name":"run_commands"}}],
		"tool_choice":"auto"
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"tool_calls"`) || !strings.Contains(body, `"call_1"`) {
		t.Fatalf("stream did not pass through native tool_calls:\n%s", body)
	}
	if strings.Contains(body, "<tool_call") || strings.Contains(body, `\u003ctool_call`) {
		t.Fatalf("stream leaked raw text tool call:\n%s", body)
	}
}

func TestChatCompletionsToolsStreamNativeToolCallPreservesLeadingProse(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "선행 prose\n\n<tool_call><function=run_commands><parameter=commands>[\"git status\"]</parameter></function></tool_call>"}
	fake.events <- &iop.RunEvent{
		Type: "complete",
		Metadata: map[string]string{
			"finish_reason":                "tool_calls",
			runtimeMetadataOpenAIToolCalls: `[{"id":"call_1","type":"function","function":{"name":"run_commands","arguments":"{\"commands\":[\"git status\"]}"}}]`,
		},
	}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"metadata":{},
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"status"}],
		"tools":[{"type":"function","function":{"name":"run_commands"}}],
		"tool_choice":"auto"
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"content":"선행 prose"`) {
		t.Fatalf("stream dropped leading prose:\n%s", body)
	}
	if !strings.Contains(body, `"tool_calls"`) || !strings.Contains(body, `"call_1"`) {
		t.Fatalf("stream did not pass through native tool_calls:\n%s", body)
	}
	if strings.Contains(body, "<tool_call") || strings.Contains(body, `\u003ctool_call`) {
		t.Fatalf("stream leaked raw text tool call:\n%s", body)
	}
}

func TestChatCompletionsStreamDropsPartialTextToolCallSuffixWhenNativeToolCallsArrive(t *testing.T) {
	cases := []struct {
		name        string
		delta       string
		wantContent string
		leakMarkers []string
	}{
		{
			name:        "xml partial candidate suffix",
			delta:       "checking status <tool_ca",
			wantContent: `"content":"checking status"`,
			leakMarkers: []string{`\u003ctool_ca`},
		},
		{
			name:        "mustache partial candidate suffix",
			delta:       "checking status {",
			wantContent: `"content":"checking status"`,
			leakMarkers: []string{`"content":"{"`},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
			fake.events <- &iop.RunEvent{Type: "delta", Delta: tc.delta}
			fake.events <- &iop.RunEvent{
				Type: "complete",
				Metadata: map[string]string{
					"finish_reason":                "tool_calls",
					runtimeMetadataOpenAIToolCalls: `[{"id":"call_1","type":"function","function":{"name":"run_commands","arguments":"{\"commands\":[\"git status\"]}"}}]`,
				},
			}

			srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
			req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"metadata":{},
				"model":"stream-model",
				"stream":true,
				"messages":[{"role":"user","content":"status"}],
				"tools":[{"type":"function","function":{"name":"run_commands"}}],
				"tool_choice":"auto"
			}`))
			w := httptest.NewRecorder()

			srv.handleChatCompletions(w, req)

			if w.Code != http.StatusOK {
				t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
			}
			body := w.Body.String()
			if !strings.Contains(body, `"tool_calls"`) || !strings.Contains(body, `"call_1"`) {
				t.Fatalf("stream did not pass through native tool_calls:\n%s", body)
			}
			if !strings.Contains(body, `"finish_reason":"tool_calls"`) {
				t.Fatalf("stream finish_reason:\n%s", body)
			}
			if !strings.Contains(body, tc.wantContent) {
				t.Fatalf("stream dropped safe prefix content %s:\n%s", tc.wantContent, body)
			}
			for _, marker := range tc.leakMarkers {
				if strings.Contains(body, marker) {
					t.Fatalf("stream leaked partial text tool call marker %q:\n%s", marker, body)
				}
			}
		})
	}
}

func TestChatCompletionsStreamingReportsClosedRunStream(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent)}
	close(fake.events)

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"message":"run stream closed"`) || !strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected SSE error body:\n%s", body)
	}
}

func TestChatCompletionsReturnsReasoningContentSeparately(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 5)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "thinking "}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "more"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "answer"}
	fake.events <- &iop.RunEvent{Type: "complete", Usage: &iop.Usage{InputTokens: 2, OutputTokens: 2}}

	srv := NewServer(config.EdgeOpenAIConf{
		Adapter: "ollama",
		Target:  "llama-fixed",
	}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"client-model",
		"messages":[{"role":"user","content":"hi"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

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

	var resp chatCompletionResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.Choices[0].Message.Content != "answer" {
		t.Fatalf("content: got %q, expected \"answer\"", resp.Choices[0].Message.Content)
	}
	if resp.Choices[0].Message.ReasoningContent != "thinking more" {
		t.Fatalf("reasoning_content: got %q, expected \"thinking more\"", resp.Choices[0].Message.ReasoningContent)
	}
}

func TestChatCompletionsFallsBackToReasoningWhenContentEmpty(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "thinking "}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "only"}
	fake.events <- &iop.RunEvent{Type: "complete", Metadata: map[string]string{"finish_reason": "length"}}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"client-model",
		"messages":[{"role":"user","content":"hi"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	var resp chatCompletionResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	content := resp.Choices[0].Message.Content
	if !strings.Contains(content, "thinking only") || !strings.Contains(content, "finish_reason=length") {
		t.Fatalf("content fallback did not preserve reasoning and finish reason: %q", content)
	}
	if resp.Choices[0].Message.ReasoningContent != "thinking only" {
		t.Fatalf("reasoning_content: got %q, expected \"thinking only\"", resp.Choices[0].Message.ReasoningContent)
	}
}

func TestChatCompletionsSuppressesReasoningContentWhenExcluded(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 5)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "thinking "}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "more"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "answer"}
	fake.events <- &iop.RunEvent{Type: "complete", Usage: &iop.Usage{InputTokens: 2, OutputTokens: 2}}

	srv := NewServer(config.EdgeOpenAIConf{
		Adapter: "ollama",
		Target:  "llama-fixed",
	}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"client-model",
		"messages":[{"role":"user","content":"hi"}],
		"include_reasoning": false
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

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

	var resp chatCompletionResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.Choices[0].Message.Content != "answer" {
		t.Fatalf("content: got %q, expected \"answer\"", resp.Choices[0].Message.Content)
	}
	if resp.Choices[0].Message.ReasoningContent != "" {
		t.Fatalf("reasoning_content: got %q, expected empty string due to include_reasoning=false", resp.Choices[0].Message.ReasoningContent)
	}
}

func TestChatCompletionsReportsHiddenReasoningWhenExcludedAndContentEmpty(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "thinking "}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "only"}
	fake.events <- &iop.RunEvent{Type: "complete", Metadata: map[string]string{"finish_reason": "length"}}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"client-model",
		"messages":[{"role":"user","content":"hi"}],
		"include_reasoning": false
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	var resp chatCompletionResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	content := resp.Choices[0].Message.Content
	if content == "" {
		t.Fatalf("content should report hidden reasoning instead of returning empty success")
	}
	if strings.Contains(content, "thinking only") {
		t.Fatalf("hidden reasoning fallback leaked reasoning content: %q", content)
	}
	if !strings.Contains(content, "reasoning was hidden by include_reasoning=false") || !strings.Contains(content, "finish_reason=length") {
		t.Fatalf("hidden reasoning fallback did not report cause and finish reason: %q", content)
	}
	if resp.Choices[0].Message.ReasoningContent != "" {
		t.Fatalf("reasoning_content: got %q, expected empty string due to include_reasoning=false", resp.Choices[0].Message.ReasoningContent)
	}
}

func TestChatCompletionsStreamsReasoningSSE(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "think"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "hi"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"reasoning_content":"think"`) || !strings.Contains(body, `"content":"hi"`) || !strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected SSE body:\n%s", body)
	}
}

func TestChatCompletionsStrictOutputStreamsExplicitReasoningSSE(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "think"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "<attempt_completion><result>hi</result></attempt_completion>"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}],
		"include_reasoning": true
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"reasoning_content":"think"`) ||
		!strings.Contains(body, `"content":"\u003cattempt_completion\u003e\u003cresult\u003ehi\u003c/result\u003e\u003c/attempt_completion\u003e"`) ||
		!strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected SSE body:\n%s", body)
	}
}

func TestChatCompletionsStreamFallsBackToReasoningWhenContentEmpty(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "think only"}
	fake.events <- &iop.RunEvent{Type: "complete", Metadata: map[string]string{"finish_reason": "length"}}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if !strings.Contains(body, `"reasoning_content":"think only"`) ||
		!strings.Contains(body, `"content":"think only`) ||
		!strings.Contains(body, "finish_reason=length") ||
		!strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected SSE body:\n%s", body)
	}
}

func TestChatCompletionsStreamSuppressesReasoningWhenExcluded(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "think"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "hi"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}],
		"include_reasoning": false
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if strings.Contains(body, "reasoning_content") || strings.Contains(body, "think") {
		t.Fatalf("unexpected reasoning_content in SSE body when excluded:\n%s", body)
	}
	if !strings.Contains(body, `"content":"hi"`) || !strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected SSE body, missing content or DONE:\n%s", body)
	}
}

func TestChatCompletionsStreamSanitizesKnownSentinelTokenAcrossDeltas(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 5)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "think <|mask"}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "_end|>"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "hello <|mask"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "_end|> world"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"metadata":{},
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if strings.Contains(body, "<|mask") || strings.Contains(body, "mask_end") {
		t.Fatalf("stream leaked sentinel token:\n%s", body)
	}
	if !strings.Contains(body, `"content":"hello "`) || !strings.Contains(body, `"content":"world"`) {
		t.Fatalf("stream did not preserve sanitized content chunks:\n%s", body)
	}
	if !strings.Contains(body, `"reasoning_content":"think "`) || !strings.Contains(body, "data: [DONE]") {
		t.Fatalf("stream did not preserve sanitized reasoning or done marker:\n%s", body)
	}
}

func TestChatCompletionsStreamReportsHiddenReasoningWhenExcludedAndContentEmpty(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "think only"}
	fake.events <- &iop.RunEvent{Type: "complete", Metadata: map[string]string{"finish_reason": "length"}}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama"}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"stream-model",
		"stream":true,
		"messages":[{"role":"user","content":"hi"}],
		"include_reasoning": false
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if strings.Contains(body, "reasoning_content") || strings.Contains(body, "think only") {
		t.Fatalf("unexpected reasoning fallback in SSE body when excluded:\n%s", body)
	}
	if !strings.Contains(body, "reasoning was hidden by include_reasoning=false") ||
		!strings.Contains(body, "finish_reason=length") ||
		!strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected SSE body, missing hidden reasoning report or DONE:\n%s", body)
	}
}

func TestChatCompletionsStrictOutputNormalizesXMLStyleAgentResponse(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "---\n<thinking>hidden</thinking>\n\n<attempt_completion>\n<result>ok</result>\n</attempt completion>\n```\ntrailing noise"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"agent-model",
		"messages":[{"role":"user","content":"finish"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	if fake.req.Input["think"] != false {
		t.Fatalf("strict output should disable thinking by default: %+v", fake.req.Input)
	}
	var resp chatCompletionResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	want := "<attempt_completion>\n<result>ok</result>\n</attempt_completion>"
	if resp.Choices[0].Message.Content != want {
		t.Fatalf("content:\ngot  %q\nwant %q", resp.Choices[0].Message.Content, want)
	}
	if resp.Choices[0].Message.ReasoningContent != "" {
		t.Fatalf("strict output should drop reasoning content: %q", resp.Choices[0].Message.ReasoningContent)
	}
}

func TestChatCompletionsStrictOutputPreservesExplicitReasoningContent(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "hidden thought"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "<attempt_completion>\n<result>ok</result>\n</attempt_completion>"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"agent-model",
		"messages":[{"role":"user","content":"finish"}],
		"include_reasoning": true
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	var resp chatCompletionResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.Choices[0].Message.Content != "<attempt_completion>\n<result>ok</result>\n</attempt_completion>" {
		t.Fatalf("content: %q", resp.Choices[0].Message.Content)
	}
	if resp.Choices[0].Message.ReasoningContent != "hidden thought" {
		t.Fatalf("reasoning_content: got %q, want hidden thought", resp.Choices[0].Message.ReasoningContent)
	}
}

func TestChatCompletionsStrictOutputWrapsPlainTextWhenPromptDeclaresCompletionTool(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "plain answer"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"agent-model",
		"messages":[
			{"role":"system","content":"Once you've completed the user's task, you must use the attempt_completion tool to present the result.\n\n<attempt_completion>\n<result>done</result>\n</attempt_completion>"},
			{"role":"user","content":"finish"}
		]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	if !strings.Contains(fake.req.Prompt, "output exactly one <attempt_completion> block") || !strings.Contains(fake.req.Prompt, "actual user-facing response") {
		t.Fatalf("strict contract instruction not injected:\n%s", fake.req.Prompt)
	}
	var resp chatCompletionResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	want := "<attempt_completion>\n<result>plain answer</result>\n</attempt_completion>"
	if resp.Choices[0].Message.Content != want {
		t.Fatalf("content:\ngot  %q\nwant %q", resp.Choices[0].Message.Content, want)
	}
}

func TestChatCompletionsStrictOutputBuffersStreamingResponse(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 4)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "hidden"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "---\n<think>noise</think><read_file><path>a.go</path></read file>"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true, StrictStreamBuffer: true}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"agent-model",
		"stream":true,
		"messages":[{"role":"user","content":"read"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if strings.Contains(body, "reasoning_content") || strings.Contains(body, "<think>") || strings.Contains(body, "</read file>") {
		t.Fatalf("strict stream leaked unstable content:\n%s", body)
	}
	if !strings.Contains(body, "read_file") || !strings.Contains(body, "a.go") || !strings.Contains(body, "data: [DONE]") {
		t.Fatalf("unexpected strict SSE body:\n%s", body)
	}
}

func TestChatCompletionsStrictOutputStreamsLiveContent(t *testing.T) {
	fake := &fakeRunService{events: make(chan *iop.RunEvent, 5)}
	fake.events <- &iop.RunEvent{Type: "reasoning_delta", Delta: "hidden"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "<attempt_completion>\n"}
	fake.events <- &iop.RunEvent{Type: "delta", Delta: "<result>ok</result>\n</attempt_completion>"}
	fake.events <- &iop.RunEvent{Type: "complete"}

	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", StrictOutput: true}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
		"model":"agent-model",
		"stream":true,
		"messages":[{"role":"user","content":"finish"}]
	}`))
	w := httptest.NewRecorder()

	srv.handleChatCompletions(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	body := w.Body.String()
	if strings.Contains(body, "reasoning_content") || strings.Contains(body, "hidden") {
		t.Fatalf("strict stream leaked reasoning content:\n%s", body)
	}
	if strings.Count(body, `"content":`) != 2 || !strings.Contains(body, "attempt_completion") || !strings.Contains(body, "data: [DONE]") {
		t.Fatalf("strict stream should pass live content deltas:\n%s", body)
	}
}
