// Copyright (c) 2026 Alibaba Group and its affiliates

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

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

	"github.com/dashscope/dashscope-confidential-maas/sdk"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestApplyChatParameters(t *testing.T) {
	body := []byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"stream": true,
		"max_tokens": 128,
		"max_completion_tokens": 64,
		"temperature": 0,
		"top_p": 0.9,
		"frequency_penalty": 0.1,
		"presence_penalty": 0.2,
		"top_k": 20,
		"stop": ["END", "STOP"],
			"seed": 7,
			"n": 1,
			"logprobs": true,
			"top_logprobs": 2,
			"parallel_tool_calls": true,
			"stream_options": {"include_usage": true},
		"chat_template_kwargs": {"enable_thinking": false},
		"thinking_budget": 20
	}`)

	var req openAIChatRequest
	require.NoError(t, json.Unmarshal(body, &req))

	sdkReq := &sdk.ChatCompletionRequest{Stream: req.Stream}
	require.NoError(t, applyChatParameters(&req, sdkReq))

	require.NotNil(t, sdkReq.MaxTokens)
	assert.Equal(t, 64, *sdkReq.MaxTokens)
	require.NotNil(t, sdkReq.Temperature)
	assert.Equal(t, 0.0, *sdkReq.Temperature)
	require.NotNil(t, sdkReq.TopP)
	assert.Equal(t, 0.9, *sdkReq.TopP)
	require.NotNil(t, sdkReq.FrequencyPenalty)
	assert.Equal(t, 0.1, *sdkReq.FrequencyPenalty)
	require.NotNil(t, sdkReq.PresencePenalty)
	assert.Equal(t, 0.2, *sdkReq.PresencePenalty)
	require.NotNil(t, sdkReq.TopK)
	assert.Equal(t, 20, *sdkReq.TopK)
	assert.Equal(t, []string{"END", "STOP"}, sdkReq.Stop)
	require.NotNil(t, sdkReq.Seed)
	assert.Equal(t, 7, *sdkReq.Seed)
	require.NotNil(t, sdkReq.N)
	assert.Equal(t, 1, *sdkReq.N)
	require.NotNil(t, sdkReq.Logprobs)
	assert.True(t, *sdkReq.Logprobs)
	require.NotNil(t, sdkReq.TopLogprobs)
	assert.Equal(t, 2, *sdkReq.TopLogprobs)
	require.NotNil(t, sdkReq.ParallelToolCalls)
	assert.True(t, *sdkReq.ParallelToolCalls)
	require.NotNil(t, sdkReq.StreamOptions)
	assert.True(t, sdkReq.StreamOptions.IncludeUsage)
	require.NotNil(t, sdkReq.ThinkingTokenBudget)
	assert.Equal(t, 20, *sdkReq.ThinkingTokenBudget)
	assert.Equal(t, false, sdkReq.ChatTemplateKwargs["enable_thinking"])
}

func TestApplyChatParametersStopString(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{"stop":"END"}`), &req))

	sdkReq := &sdk.ChatCompletionRequest{}
	require.NoError(t, applyChatParameters(&req, sdkReq))

	assert.Equal(t, []string{"END"}, sdkReq.Stop)
}

func TestApplyChatParametersStopTokenIDs(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{"stop":["END",109266]}`), &req))

	sdkReq := &sdk.ChatCompletionRequest{}
	require.NoError(t, applyChatParameters(&req, sdkReq))

	assert.Equal(t, []string{"END"}, sdkReq.Stop)
	assert.Equal(t, []int{109266}, sdkReq.StopTokenIDs)
}

func TestApplyChatParametersMapsMaxLengthAlias(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{"max_length":5}`), &req))

	sdkReq := &sdk.ChatCompletionRequest{}
	require.NoError(t, applyChatParameters(&req, sdkReq))

	require.NotNil(t, sdkReq.MaxTokens)
	assert.Equal(t, 5, *sdkReq.MaxTokens)
}

func TestApplyChatParametersPrefersStandardMaxTokens(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{"max_tokens":7,"max_length":5}`), &req))

	sdkReq := &sdk.ChatCompletionRequest{}
	require.NoError(t, applyChatParameters(&req, sdkReq))

	require.NotNil(t, sdkReq.MaxTokens)
	assert.Equal(t, 7, *sdkReq.MaxTokens)
}

func TestApplyChatParametersMapsTopLevelEnableThinking(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{"enable_thinking":false}`), &req))

	sdkReq := &sdk.ChatCompletionRequest{}
	require.NoError(t, applyChatParameters(&req, sdkReq))

	require.NotNil(t, sdkReq.ChatTemplateKwargs)
	assert.Equal(t, false, sdkReq.ChatTemplateKwargs["enable_thinking"])
}

func TestApplyChatParametersAllowsMatchingEnableThinking(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{
		"enable_thinking": false,
		"chat_template_kwargs": {"enable_thinking": false}
	}`), &req))

	sdkReq := &sdk.ChatCompletionRequest{}
	require.NoError(t, applyChatParameters(&req, sdkReq))
	assert.Equal(t, false, sdkReq.ChatTemplateKwargs["enable_thinking"])
}

func TestApplyChatParametersRejectsConflictingEnableThinking(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{
		"enable_thinking": true,
		"chat_template_kwargs": {"enable_thinking": false}
	}`), &req))

	err := applyChatParameters(&req, &sdk.ChatCompletionRequest{})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "conflicts")
}

func TestApplyChatParametersIgnoresZeroNAndTopLogprobsWithoutLogprobs(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{
		"n": 0,
		"top_p": 0,
		"logprobs": false,
		"top_logprobs": 2
	}`), &req))

	sdkReq := &sdk.ChatCompletionRequest{}
	require.NoError(t, applyChatParameters(&req, sdkReq))
	assert.Nil(t, sdkReq.N)
	require.NotNil(t, sdkReq.Logprobs)
	assert.False(t, *sdkReq.Logprobs)
	assert.Nil(t, sdkReq.TopLogprobs)
	assert.Nil(t, sdkReq.TopP)
}

func TestValidateChatRequestSemanticsRejectsNRangeWithModelStudioCode(t *testing.T) {
	var req openAIChatRequest
	require.NoError(t, json.Unmarshal([]byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"n": 5
	}`), &req))

	err := validateChatRequestSemantics(&req)
	require.Error(t, err)
	var fieldErr *requestFieldError
	require.ErrorAs(t, err, &fieldErr)
	assert.Equal(t, "integer_below_min_value", fieldErr.Code)
	assert.Equal(t, "<400> InternalError.Algo.InvalidParameter: Range of n should be [1, 4]", fieldErr.Message)
}

func TestConvertOpenAIMessagesAppendsEmptyUserWhenMissing(t *testing.T) {
	msgs := convertOpenAIMessagesToSDK([]openAIMessage{
		{Role: "system", Content: "You are helpful."},
	})

	require.Len(t, msgs, 2)
	assert.Equal(t, "system", msgs[0].Role)
	assert.Equal(t, "user", msgs[1].Role)
	assert.Equal(t, "", msgs[1].Content)
}

func TestConvertOpenAIMessagesKeepsExistingUser(t *testing.T) {
	msgs := convertOpenAIMessagesToSDK([]openAIMessage{
		{Role: "system", Content: "You are helpful."},
		{Role: "user", Content: "hi"},
	})

	require.Len(t, msgs, 2)
	assert.Equal(t, "user", msgs[1].Role)
	assert.Equal(t, "hi", msgs[1].Content)
}

func TestNormalizedReasoningContent(t *testing.T) {
	assert.Equal(t, "standard", normalizedReasoningContent(&sdk.ChatCompletion{
		ReasoningContent: "standard",
		Reasoning:        "alias",
	}))
	assert.Equal(t, "alias", normalizedReasoningContent(&sdk.ChatCompletion{Reasoning: "alias"}))
}

func TestValidateChatRequestFieldsAllowsSupportedAndPlatformFields(t *testing.T) {
	body := []byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"temperature": 0,
		"logprobs": false,
		"top_logprobs": 2,
		"enable_thinking": false,
		"chat_template_kwargs": {"enable_thinking": false},
		"dashscope_custom": {},
		"debug": true,
		"head_response_per_tokens": 1,
		"include_chunk_usage": true,
		"incremental_output": true,
		"max_input_tokens": 100000,
		"max_length": 2048,
		"output_in_full": true,
		"output_tokens_per_response": 10,
		"parallel_tool_calls ": true,
		"prompt": "legacy prompt",
		"result_format": "message",
		"task_group": "aigc",
		"use_raw_prompt": false
	}`)

	_, err := validateChatRequestFields(body)
	assert.NoError(t, err)
}

func TestValidateChatRequestFieldsTrimsNames(t *testing.T) {
	body := []byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"parallel_tool_calls ": false
	}`)

	normalized, err := validateChatRequestFields(body)
	require.NoError(t, err)

	var req openAIChatRequest
	require.NoError(t, json.Unmarshal(normalized, &req))
	require.NotNil(t, req.ParallelToolCalls)
	assert.False(t, *req.ParallelToolCalls)
}

func TestValidateChatRequestFieldsRejectsTrimmedDuplicates(t *testing.T) {
	_, err := validateChatRequestFields([]byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"parallel_tool_calls": false,
		"parallel_tool_calls ": true
	}`))

	require.Error(t, err)
	assert.Contains(t, err.Error(), "duplicate request field")
}

func TestOpenAILogprobs(t *testing.T) {
	assert.Nil(t, openAILogprobs(nil))
	assert.Nil(t, openAILogprobs(json.RawMessage(`null`)))

	value := openAILogprobs(json.RawMessage(`{"content":[{"token":"hello","logprob":-0.1}]}`))
	asMap, ok := value.(map[string]interface{})
	require.True(t, ok)
	content, ok := asMap["content"].([]interface{})
	require.True(t, ok)
	require.Len(t, content, 1)
}

func TestWriteResponseUsesCompletionFinishReason(t *testing.T) {
	completion := &sdk.ChatCompletion{
		Content:      "hello",
		FinishReason: "length",
		Usage: sdk.TokenUsage{
			PromptTokens:     1,
			CompletionTokens: 2,
			TotalTokens:      3,
			CompletionTokensDetails: map[string]interface{}{
				"reasoning_tokens": 1,
				"text_tokens":      1,
			},
		},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeResponse(rr, completion, "chatcmpl-test", 123, "main", false, false, false)

	var resp openAIResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
	require.Len(t, resp.Choices, 1)
	require.NotNil(t, resp.Choices[0].FinishReason)
	assert.Equal(t, "length", *resp.Choices[0].FinishReason)
	assert.Equal(t, "hello", resp.Choices[0].Message.Content)
	assert.Nil(t, resp.Choices[0].Logprobs)
	assert.Equal(t, 1, resp.Usage.PromptTokens)
	assert.Equal(t, 2, resp.Usage.CompletionTokens)
	assert.Equal(t, 3, resp.Usage.TotalTokens)
	assert.Equal(t, float64(1), resp.Usage.CompletionTokensDetails["reasoning_tokens"])
	assert.Equal(t, float64(1), resp.Usage.CompletionTokensDetails["text_tokens"])
}

func TestWriteResponseLeavesMissingFinishReasonNull(t *testing.T) {
	completion := &sdk.ChatCompletion{Content: "hello"}

	rr := httptest.NewRecorder()
	(&handler{}).writeResponse(rr, completion, "chatcmpl-test", 123, "main", false, false, false)

	var resp openAIResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
	require.Len(t, resp.Choices, 1)
	assert.Nil(t, resp.Choices[0].FinishReason)
}

func TestWriteResponseUsesCompletionChoices(t *testing.T) {
	completion := &sdk.ChatCompletion{
		Choices: []sdk.ChatCompletionChoice{
			{Index: 0, Content: "first", FinishReason: "stop", Logprobs: json.RawMessage(`null`)},
			{Index: 1, Content: "second", Reasoning: "thinking", FinishReason: "length"},
		},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeResponse(rr, completion, "chatcmpl-test", 123, "main", false, false, false)

	var resp openAIResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
	require.Len(t, resp.Choices, 2)
	assert.Equal(t, 0, resp.Choices[0].Index)
	assert.Equal(t, "first", resp.Choices[0].Message.Content)
	require.NotNil(t, resp.Choices[0].FinishReason)
	assert.Equal(t, "stop", *resp.Choices[0].FinishReason)
	assert.Nil(t, resp.Choices[0].Logprobs)
	assert.Equal(t, 1, resp.Choices[1].Index)
	assert.Equal(t, "second", resp.Choices[1].Message.Content)
	assert.Equal(t, "thinking", resp.Choices[1].Message.ReasoningContent)
	require.NotNil(t, resp.Choices[1].FinishReason)
	assert.Equal(t, "length", *resp.Choices[1].FinishReason)
}

func TestWriteResponseMapsLegacyFunctionCallFinishReason(t *testing.T) {
	completion := &sdk.ChatCompletion{
		ToolCalls: []sdk.ToolCall{{
			ID:   "call_1",
			Type: "function",
			Function: sdk.FunctionCall{
				Name:      "lookup",
				Arguments: `{"q":"hi"}`,
			},
		}},
		FinishReason: "tool_calls",
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeResponse(rr, completion, "chatcmpl-test", 123, "main", false, true, false)

	var resp openAIResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
	require.Len(t, resp.Choices, 1)
	require.NotNil(t, resp.Choices[0].Message.FunctionCall)
	require.NotNil(t, resp.Choices[0].FinishReason)
	assert.Equal(t, "function_call", *resp.Choices[0].FinishReason)
}

func TestWriteResponseToolChoiceNoneSuppressesToolFinishReason(t *testing.T) {
	completion := &sdk.ChatCompletion{
		ToolCalls: []sdk.ToolCall{{
			ID:   "call_1",
			Type: "function",
			Function: sdk.FunctionCall{
				Name:      "lookup",
				Arguments: `{"q":"hi"}`,
			},
		}},
		FinishReason: "tool_calls",
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeResponse(rr, completion, "chatcmpl-test", 123, "main", true, false, false)

	var resp openAIResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
	require.Len(t, resp.Choices, 1)
	assert.Empty(t, resp.Choices[0].Message.ToolCalls)
	require.NotNil(t, resp.Choices[0].FinishReason)
	assert.Equal(t, "stop", *resp.Choices[0].FinishReason)
}

func TestValidateChatRequestFieldsRejectsUnsupportedSemanticField(t *testing.T) {
	_, err := validateChatRequestFields([]byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"audio": {}
	}`))

	require.Error(t, err)
	assert.Contains(t, err.Error(), `"audio"`)
}

func TestValidateChatRequestFieldsRejectsUnsupportedStandardFields(t *testing.T) {
	// "store" was previously rejected but is now silently accepted as a
	// compatible OpenAI hosted-platform field (no storage layer to drive it).
	for _, field := range []string{"metadata", "service_tier"} {
		t.Run(field, func(t *testing.T) {
			_, err := validateChatRequestFields([]byte(fmt.Sprintf(`{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"%s": "value"
			}`, field)))

			require.Error(t, err)
			assert.Contains(t, err.Error(), field)
		})
	}
}

func TestValidateChatRequestFieldsRejectsUnsupportedExtensionFields(t *testing.T) {
	for _, field := range []string{"enable_serach"} {
		t.Run(field, func(t *testing.T) {
			_, err := validateChatRequestFields([]byte(fmt.Sprintf(`{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"%s": true
			}`, field)))

			require.Error(t, err)
			assert.Contains(t, err.Error(), field)
		})
	}
}

func TestValidateChatRequestFieldsAcceptsCompatibleAndForwardedFields(t *testing.T) {
	body := []byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"user": "user_123",
		"repetition_penalty": 1.1,
		"thinking_budget": 20
	}`)

	_, err := validateChatRequestFields(body)
	assert.NoError(t, err)
}

func TestValidateChatRequestFieldsRejectsUnknownField(t *testing.T) {
	_, err := validateChatRequestFields([]byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "hi"}],
		"future_sensitive_field": true
	}`))

	require.Error(t, err)
	assert.Contains(t, err.Error(), `"future_sensitive_field"`)
}

func TestValidateChatRequestSemanticsRejectsInvalidCombinations(t *testing.T) {
	tests := []struct {
		name string
		body string
		want string
	}{
		{
			name: "missing model",
			body: `{
				"messages": [{"role": "user", "content": "hi"}]
			}`,
			want: "required model is missing",
		},
		{
			name: "invalid role",
			body: `{
				"model": "main",
				"messages": [{"role": "xxx", "content": "hi"}]
			}`,
			want: "['system', 'assistant', 'user', 'tool', 'function']",
		},
		{
			name: "n below zero",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"n": -1
			}`,
			want: "Range of n should be [1, 4]",
		},
		{
			name: "n above maximum",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"n": 5
			}`,
			want: "Range of n should be [1, 4]",
		},
		{
			name: "max length below one",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"max_length": 0
			}`,
			want: "max_length must be a positive integer",
		},
		{
			name: "thinking budget below one",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"thinking_budget": -1
			}`,
			want: "thinking_budget must be a positive integer",
		},
		{
			name: "temperature outside accepted range",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"temperature": 4
			}`,
			want: "<400> InternalError.Algo.InvalidParameter: Temperature should be in [0.0, 2.0)",
		},
		{
			name: "temperature equal upper bound",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"temperature": 2
			}`,
			want: "<400> InternalError.Algo.InvalidParameter: Temperature should be in [0.0, 2.0)",
		},
		{
			name: "top p below zero",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"top_p": -0.1
			}`,
			want: "'top_p' must be in (0.0, 1.0]",
		},
		{
			name: "top p above one",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"top_p": 1.1
			}`,
			want: "'top_p' must be in (0.0, 1.0]",
		},
		{
			name: "stream options without stream",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"stream_options": {"include_usage": true}
			}`,
			want: "stream_options can only be set when stream is true",
		},
		{
			name: "thinking with multiple choices",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"enable_thinking": true,
				"n": 2
			}`,
			want: "<400> InternalError.Algo.InvalidParameter: The n parameter must be 1 when enable_thinking is true",
		},
		{
			name: "thinking mode rejects required tool choice",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"enable_thinking": true,
				"tool_choice": "required"
			}`,
			want: "tool_choice parameter does not support",
		},
		{
			name: "thinking mode rejects object tool choice",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"enable_thinking": true,
				"tool_choice": {"type":"function","function":{"name":"lookup"}}
			}`,
			want: "tool_choice parameter does not support",
		},
		{
			name: "response format must be object",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"response_format": "json_object"
			}`,
			want: "response_format must be a valid dictionary",
		},
		{
			name: "json object cannot carry schema",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "output JSON"}],
				"response_format": {"type": "json_object", "json_schema": {"name": "x"}}
			}`,
			want: "<400> InternalError.Algo.InvalidParameter: Unknown parameter: 'response_format.json_schema'. 'response_format.json_schema' cannot be provided when 'response_format.type' is 'json_object'.",
		},
		{
			name: "json object requires json instruction",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"response_format": {"type": "json_object"}
			}`,
			want: "must contain the word 'json'",
		},
		{
			name: "unknown response format type",
			body: `{
				"model": "main",
				"messages": [{"role": "user", "content": "hi"}],
				"response_format": {"type": "aaa"}
			}`,
			want: "<400> InternalError.Algo.InvalidParameter: 'response_format.type' Invalid value: 'aaa'. Supported values are: 'json_object' and 'text'.",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var req openAIChatRequest
			require.NoError(t, json.Unmarshal([]byte(tt.body), &req))
			err := validateChatRequestSemantics(&req)
			require.Error(t, err)
			assert.Contains(t, err.Error(), tt.want)
		})
	}
}

func TestValidateChatRequestSemanticsAllowsValidCombinations(t *testing.T) {
	body := []byte(`{
		"model": "main",
		"messages": [{"role": "user", "content": "please output JSON"}],
		"stream": true,
		"stream_options": {"include_usage": true},
		"temperature": 1.9,
		"logprobs": false,
		"top_logprobs": 2,
		"enable_thinking": true,
		"n": 0,
		"response_format": {"type": "json_object"}
	}`)

	var req openAIChatRequest
	require.NoError(t, json.Unmarshal(body, &req))
	assert.NoError(t, validateChatRequestSemantics(&req))
}

func TestWriteStreamSendsSeparateUsageChunk(t *testing.T) {
	chunks := make(chan string, 1)
	chunks <- "hello"
	close(chunks)

	completion := &sdk.ChatCompletion{
		Chunks: chunks,
		Usage: sdk.TokenUsage{
			PromptTokens:     1,
			CompletionTokens: 2,
			TotalTokens:      3,
			CompletionTokensDetails: map[string]interface{}{
				"reasoning_tokens": 1,
				"text_tokens":      1,
			},
		},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", true, false, false, false)

	assert.Equal(t, "text/event-stream; charset=utf-8", rr.Header().Get("Content-Type"))

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 4)

	var roleChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[0]), &roleChunk))
	assertChoiceHasNullLogprobs(t, roleChunk, 0)
	assertStreamChoiceOmitsMessage(t, roleChunk, 0)
	roleChoices := roleChunk["choices"].([]interface{})
	roleDelta := roleChoices[0].(map[string]interface{})["delta"].(map[string]interface{})
	assert.Equal(t, "assistant", roleDelta["role"])
	assert.Equal(t, "", roleDelta["content"])

	var contentChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[1]), &contentChunk))
	assertChoiceHasNullLogprobs(t, contentChunk, 0)
	assertStreamChoiceOmitsMessage(t, contentChunk, 0)

	var finishChunk openAIChunk
	require.NoError(t, json.Unmarshal([]byte(events[2]), &finishChunk))
	require.Len(t, finishChunk.Choices, 1)
	require.Nil(t, finishChunk.Usage)
	require.NotNil(t, finishChunk.Choices[0].FinishReason)
	assert.Equal(t, "stop", *finishChunk.Choices[0].FinishReason)

	var usageChunk openAIChunk
	require.NoError(t, json.Unmarshal([]byte(events[3]), &usageChunk))
	assert.Empty(t, usageChunk.Choices)
	require.NotNil(t, usageChunk.Usage)
	assert.Equal(t, 1, usageChunk.Usage.PromptTokens)
	assert.Equal(t, 2, usageChunk.Usage.CompletionTokens)
	assert.Equal(t, 3, usageChunk.Usage.TotalTokens)
	assert.Equal(t, float64(1), usageChunk.Usage.CompletionTokensDetails["reasoning_tokens"])
	assert.Equal(t, float64(1), usageChunk.Usage.CompletionTokensDetails["text_tokens"])
}

func TestWriteStreamOmitsUsageChunkWhenNotRequested(t *testing.T) {
	chunks := make(chan string)
	close(chunks)

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, &sdk.ChatCompletion{Chunks: chunks}, "chatcmpl-test", 123, "main", false, false, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 2)

	var finishChunk openAIChunk
	require.NoError(t, json.Unmarshal([]byte(events[1]), &finishChunk))
	require.Len(t, finishChunk.Choices, 1)
	assert.Nil(t, finishChunk.Usage)
}

func TestWriteStreamSendsSeparateFinishChunksForMultipleChoices(t *testing.T) {
	completion := &sdk.ChatCompletion{
		FinishChoices: []sdk.ChatCompletionChoice{
			{Index: 0, FinishReason: "stop"},
			{Index: 1, FinishReason: "stop"},
			{Index: 2, FinishReason: "stop"},
		},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 4)

	for i := 0; i < 3; i++ {
		var finishChunk openAIChunk
		require.NoError(t, json.Unmarshal([]byte(events[i+1]), &finishChunk))
		require.Len(t, finishChunk.Choices, 1)
		assert.Equal(t, i, finishChunk.Choices[0].Index)
		require.NotNil(t, finishChunk.Choices[0].FinishReason)
		assert.Equal(t, "stop", *finishChunk.Choices[0].FinishReason)
	}
}

func TestWriteStreamUsesStructuredDeltasWithLogprobs(t *testing.T) {
	deltas := make(chan sdk.StreamDelta, 1)
	deltas <- sdk.StreamDelta{
		Content:  "hello",
		Logprobs: json.RawMessage(`{"content":[{"token":"hello","logprob":-0.1}]}`),
	}
	close(deltas)

	completion := &sdk.ChatCompletion{
		Deltas:         deltas,
		FinishReason:   "stop",
		FinishLogprobs: json.RawMessage(`null`),
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 3)

	var contentChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[1]), &contentChunk))
	choices := contentChunk["choices"].([]interface{})
	choice := choices[0].(map[string]interface{})
	assert.NotContains(t, choice, "message")
	delta := choice["delta"].(map[string]interface{})
	assert.NotContains(t, delta, "role")
	logprobs := choice["logprobs"].(map[string]interface{})
	require.Contains(t, logprobs, "content")

	var finishChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[2]), &finishChunk))
	assertChoiceHasNullLogprobs(t, finishChunk, 0)
}

func TestWriteStreamSkipsEmptyNullLogprobsDeltas(t *testing.T) {
	deltas := make(chan sdk.StreamDelta, 2)
	deltas <- sdk.StreamDelta{Logprobs: json.RawMessage(`null`)}
	deltas <- sdk.StreamDelta{Content: "hello"}
	close(deltas)

	completion := &sdk.ChatCompletion{
		Deltas:       deltas,
		FinishReason: "stop",
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 3)

	var contentChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[1]), &contentChunk))
	choices := contentChunk["choices"].([]interface{})
	choice := choices[0].(map[string]interface{})
	delta := choice["delta"].(map[string]interface{})
	assert.Equal(t, "hello", delta["content"])
}

func TestWriteStreamPreservesBackendToolCallFinishReason(t *testing.T) {
	toolChunks := make(chan sdk.ToolCallDelta, 1)
	toolChunks <- sdk.ToolCallDelta{ChoiceIndex: 0, Index: 0, ID: "call_1", Type: "function", Name: "lookup", Arguments: `{"q":"hi"}`}
	close(toolChunks)

	completion := &sdk.ChatCompletion{
		ToolCallChunks: toolChunks,
		FinishChoices:  []sdk.ChatCompletionChoice{{Index: 0, FinishReason: "stop"}},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 2)

	var toolChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[0]), &toolChunk))
	choices := toolChunk["choices"].([]interface{})
	choice := choices[0].(map[string]interface{})
	delta := choice["delta"].(map[string]interface{})
	assert.Equal(t, "assistant", delta["role"])
	assert.Contains(t, delta, "tool_calls")

	var finishChunk openAIChunk
	require.NoError(t, json.Unmarshal([]byte(events[len(events)-1]), &finishChunk))
	require.Len(t, finishChunk.Choices, 1)
	require.NotNil(t, finishChunk.Choices[0].FinishReason)
	assert.Equal(t, "stop", *finishChunk.Choices[0].FinishReason)
}

func TestWriteStreamSkipsEmptyToolCallFragments(t *testing.T) {
	toolChunks := make(chan sdk.ToolCallDelta, 2)
	toolChunks <- sdk.ToolCallDelta{ChoiceIndex: 0, Index: 0, ID: "call_1", Type: "function", Name: "lookup", Arguments: `{"q":"hi"}`}
	toolChunks <- sdk.ToolCallDelta{ChoiceIndex: 0, Index: 0}
	close(toolChunks)

	completion := &sdk.ChatCompletion{
		ToolCallChunks: toolChunks,
		FinishChoices:  []sdk.ChatCompletionChoice{{Index: 0, FinishReason: "tool_calls"}},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 2)

	var toolChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[0]), &toolChunk))
	choices := toolChunk["choices"].([]interface{})
	choice := choices[0].(map[string]interface{})
	delta := choice["delta"].(map[string]interface{})
	toolCalls := delta["tool_calls"].([]interface{})
	require.Len(t, toolCalls, 1)
	tc := toolCalls[0].(map[string]interface{})
	assert.Contains(t, tc, "function")
}

func TestWriteStreamMapsLegacyFunctionCallFinishReason(t *testing.T) {
	toolChunks := make(chan sdk.ToolCallDelta, 1)
	toolChunks <- sdk.ToolCallDelta{ChoiceIndex: 0, Index: 0, ID: "call_1", Type: "function", Name: "lookup", Arguments: `{"q":"hi"}`}
	close(toolChunks)

	completion := &sdk.ChatCompletion{
		ToolCallChunks: toolChunks,
		FinishChoices:  []sdk.ChatCompletionChoice{{Index: 0, FinishReason: "tool_calls"}},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, true, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 2)

	var functionChunk map[string]interface{}
	require.NoError(t, json.Unmarshal([]byte(events[0]), &functionChunk))
	choices := functionChunk["choices"].([]interface{})
	choice := choices[0].(map[string]interface{})
	delta := choice["delta"].(map[string]interface{})
	assert.Equal(t, "assistant", delta["role"])
	assert.Contains(t, delta, "function_call")

	var finishChunk openAIChunk
	require.NoError(t, json.Unmarshal([]byte(events[len(events)-1]), &finishChunk))
	require.Len(t, finishChunk.Choices, 1)
	require.NotNil(t, finishChunk.Choices[0].FinishReason)
	assert.Equal(t, "function_call", *finishChunk.Choices[0].FinishReason)
}

func TestWriteStreamToolChoiceNoneSuppressesToolFinishReason(t *testing.T) {
	toolChunks := make(chan sdk.ToolCallDelta, 1)
	toolChunks <- sdk.ToolCallDelta{ChoiceIndex: 0, Index: 0, ID: "call_1", Type: "function", Name: "lookup", Arguments: `{"q":"hi"}`}
	close(toolChunks)

	completion := &sdk.ChatCompletion{
		ToolCallChunks: toolChunks,
		FinishChoices:  []sdk.ChatCompletionChoice{{Index: 0, FinishReason: "tool_calls"}},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, true, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 2)

	var finishChunk openAIChunk
	require.NoError(t, json.Unmarshal([]byte(events[len(events)-1]), &finishChunk))
	require.Len(t, finishChunk.Choices, 1)
	require.NotNil(t, finishChunk.Choices[0].FinishReason)
	assert.Equal(t, "stop", *finishChunk.Choices[0].FinishReason)
}

func TestWriteStreamPreservesChoiceIndexes(t *testing.T) {
	deltas := make(chan sdk.StreamDelta, 3)
	deltas <- sdk.StreamDelta{Index: 0, Content: "hello"}
	deltas <- sdk.StreamDelta{Index: 1, Content: "hi"}
	deltas <- sdk.StreamDelta{Index: 2, Content: "hey"}
	close(deltas)

	completion := &sdk.ChatCompletion{
		Deltas: deltas,
		FinishChoices: []sdk.ChatCompletionChoice{
			{Index: 0, FinishReason: "stop"},
			{Index: 1, FinishReason: "stop"},
			{Index: 2, FinishReason: "stop"},
		},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, false, false)

	events := streamDataEvents(rr.Body.String())
	require.Len(t, events, 9)

	for i := 0; i < 3; i++ {
		var finishChunk openAIChunk
		require.NoError(t, json.Unmarshal([]byte(events[6+i]), &finishChunk))
		require.Len(t, finishChunk.Choices, 1)
		assert.Equal(t, i, finishChunk.Choices[0].Index)
		require.NotNil(t, finishChunk.Choices[0].FinishReason)
		assert.Equal(t, "stop", *finishChunk.Choices[0].FinishReason)
	}
}

func TestWriteStreamErrorBeforeFirstChunkWritesOpenAIError(t *testing.T) {
	deltas := make(chan sdk.StreamDelta)
	close(deltas)

	completion := &sdk.ChatCompletion{
		TaskID: "task-err",
		Deltas: deltas,
		Error:  &sdk.APIError{StatusCode: 400, Message: "when using `top_logprobs`, `logprobs` must be set to true"},
	}

	rr := httptest.NewRecorder()
	(&handler{}).writeStream(rr, completion, "chatcmpl-test", 123, "main", false, false, false, false)

	assert.Equal(t, 400, rr.Code)
	assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))
	assert.NotContains(t, rr.Body.String(), "data:")

	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "task-err", env.RequestID)
	assert.Equal(t, "invalid_request_error", env.Error.Type)
	assert.Equal(t, "invalid_parameter_error", env.Error.Code)
	assert.Contains(t, env.Error.Message, "top_logprobs")
}

func TestWriteOpenAIErrorModelStudioEnvelope4xx(t *testing.T) {
	rr := httptest.NewRecorder()
	writeOpenAIError(rr, 400, "Type of presence_penalty should be float", "", "")

	assert.Equal(t, 400, rr.Code)
	assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))

	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "Type of presence_penalty should be float", env.Error.Message)
	assert.Equal(t, "invalid_request_error", env.Error.Type)
	assert.Equal(t, "invalid_parameter_error", env.Error.Code)
	assert.Nil(t, env.Error.Param)
	assert.NotEmpty(t, env.RequestID)
	assert.Equal(t, "chatcmpl-"+env.RequestID, env.ID)

	// Param must serialize as JSON null (not omitted).
	var raw map[string]json.RawMessage
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &raw))
	var errObj map[string]json.RawMessage
	require.NoError(t, json.Unmarshal(raw["error"], &errObj))
	assert.Equal(t, "null", string(errObj["param"]))
}

func TestWriteOpenAIErrorModelStudioEnvelope5xx(t *testing.T) {
	rr := httptest.NewRecorder()
	writeOpenAIError(rr, 500, "boom", "", "")

	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "api_error", env.Error.Type)
	assert.Equal(t, "internal_error", env.Error.Code)
}

func TestWriteOpenAIErrorCustomCode(t *testing.T) {
	rr := httptest.NewRecorder()
	writeOpenAIError(rr, 429, "rate limit", "rate_limit_exceeded", "")

	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "rate_limit_exceeded", env.Error.Code)
	assert.Equal(t, "invalid_request_error", env.Error.Type)
}

func TestWriteOpenAIErrorUsesPassedRequestID(t *testing.T) {
	rr := httptest.NewRecorder()
	writeOpenAIError(rr, 400, "x", "", "fixed-task-id")

	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "fixed-task-id", env.RequestID)
	assert.Equal(t, "chatcmpl-fixed-task-id", env.ID)
}

func TestJSONUnmarshalMessageRewritesTypeError(t *testing.T) {
	var req openAIChatRequest
	err := json.Unmarshal([]byte(`{"presence_penalty":"a"}`), &req)
	require.Error(t, err)
	assert.Equal(t, "Type of presence_penalty should be float", jsonUnmarshalMessage(err))
}

func TestJSONUnmarshalMessageRewritesMaxLengthTypeError(t *testing.T) {
	var req openAIChatRequest
	err := json.Unmarshal([]byte(`{"max_length":"a"}`), &req)
	require.Error(t, err)
	assert.Equal(t, "max_length must be a positive integer", jsonUnmarshalMessage(err))
}

func TestJSONUnmarshalMessageRewritesTemperatureTypeError(t *testing.T) {
	var req openAIChatRequest
	err := json.Unmarshal([]byte(`{"temperature":"4.0"}`), &req)
	require.Error(t, err)
	assert.Equal(t, "'temperature' must be Float", jsonUnmarshalMessage(err))
}

func TestJSONUnmarshalMessageRewritesMessagesTypeError(t *testing.T) {
	var req openAIChatRequest
	err := json.Unmarshal([]byte(`{"messages":"[{\"role\":\"user\",\"content\":\"hi\"}]"}`), &req)
	require.Error(t, err)
	assert.Equal(t, "message must be json_object", jsonUnmarshalMessage(err))
}

func TestJSONUnmarshalMessageRewritesContentObjectError(t *testing.T) {
	err := validateInboundMessageContent([]byte(`{"messages":[
		{"role":"system","content":"hi"},
		{"role":"user","content":{"type":"text","text":"hi"}}
	]}`))
	require.Error(t, err)
	assert.Contains(t, err.Error(), "messages.1.content")
	assert.Contains(t, err.Error(), "input should be a valid string or list")
}

func TestValidateInboundMessageContentRejectsListItemsThatAreNotObjects(t *testing.T) {
	err := validateInboundMessageContent([]byte(`{"messages":[
		{"role":"user","content":"hi"},
		{"role":"assistant","content":["id","choices"]}
	]}`))
	require.Error(t, err)
	assert.Contains(t, err.Error(), "if content is list. item must be dict")
}

// TestConvertOpenAIMessagesToSDKPreservesImageURLContent verifies OpenAI image parts reach the SDK layer.
func TestConvertOpenAIMessagesToSDKPreservesImageURLContent(t *testing.T) {
	var req openAIChatRequest
	err := json.Unmarshal([]byte(`{
		"model":"main",
		"messages":[{
			"role":"user",
			"content":[
				{"type":"text","text":"describe it"},
				{"type":"image_url","image_url":{"url":"https://example.com/a.png","detail":"auto"}}
			]
		}]
	}`), &req)
	require.NoError(t, err)

	messages := convertOpenAIMessagesToSDK(req.Messages)
	require.Len(t, messages, 1)
	require.Len(t, messages[0].ContentParts, 2)
	assert.Empty(t, messages[0].Content)
	assert.Equal(t, "text", messages[0].ContentParts[0].Type)
	assert.Equal(t, "describe it", messages[0].ContentParts[0].Text)
	require.NotNil(t, messages[0].ContentParts[1].ImageURL)
	assert.Equal(t, "https://example.com/a.png", messages[0].ContentParts[1].ImageURL.URL)
	assert.Equal(t, "auto", messages[0].ContentParts[1].ImageURL.Detail)
}

func TestOpenAIToolCallAcceptsStringIndex(t *testing.T) {
	var tc openAIToolCall
	err := json.Unmarshal([]byte(`{
		"id":"call_1",
		"index":"0",
		"type":"function",
		"function":{"name":"lookup","arguments":"{}"}
	}`), &tc)
	require.NoError(t, err)
	require.NotNil(t, tc.Index)
	assert.Equal(t, 0, *tc.Index)
	assert.Equal(t, "call_1", tc.ID)
	assert.Equal(t, "function", tc.Type)
	assert.Equal(t, "lookup", tc.Function.Name)
}

func TestOpenAIToolCallRejectsNonNumericStringIndex(t *testing.T) {
	var tc openAIToolCall
	err := json.Unmarshal([]byte(`{
		"id":"call_1",
		"index":"bad",
		"type":"function",
		"function":{"name":"lookup","arguments":"{}"}
	}`), &tc)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "index must be integer")
}

func TestWriteSDKErrorMapsValidationErrorTo400(t *testing.T) {
	rr := httptest.NewRecorder()
	writeSDKError(rr, &sdk.ValidationError{Message: "messages are required"}, "task-1")

	assert.Equal(t, 400, rr.Code)
	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "messages are required", env.Error.Message)
	assert.Equal(t, "invalid_request_error", env.Error.Type)
	assert.Equal(t, "invalid_parameter_error", env.Error.Code)
	assert.Equal(t, "task-1", env.RequestID)
}

func TestWriteSDKErrorMapsAPIErrorWithStatusCode(t *testing.T) {
	rr := httptest.NewRecorder()
	writeSDKError(rr, &sdk.APIError{StatusCode: 400, Message: "Type of presence_penalty should be float"}, "task-x")

	assert.Equal(t, 400, rr.Code)
	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "Type of presence_penalty should be float", env.Error.Message)
	assert.Equal(t, "invalid_parameter_error", env.Error.Code)
	assert.Equal(t, "task-x", env.RequestID)
}

func TestWriteSDKErrorFallsBackTo500ForUnknownError(t *testing.T) {
	rr := httptest.NewRecorder()
	writeSDKError(rr, fmt.Errorf("ws closed"), "")
	assert.Equal(t, 500, rr.Code)
	var env openAIErrorResponse
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &env))
	assert.Equal(t, "api_error", env.Error.Type)
	assert.Equal(t, "internal_error", env.Error.Code)
}

func TestJSONUnmarshalMessageFallsBackToOriginal(t *testing.T) {
	var req openAIChatRequest
	err := json.Unmarshal([]byte(`not json`), &req)
	require.Error(t, err)
	assert.Contains(t, jsonUnmarshalMessage(err), "invalid JSON: ")
}

func streamDataEvents(body string) []string {
	var events []string
	for _, block := range strings.Split(body, "\n\n") {
		block = strings.TrimSpace(block)
		if !strings.HasPrefix(block, "data: ") {
			continue
		}
		data := strings.TrimPrefix(block, "data: ")
		if data == "[DONE]" {
			continue
		}
		events = append(events, data)
	}
	return events
}

func assertChoiceHasNullLogprobs(t *testing.T, chunk map[string]interface{}, index int) {
	t.Helper()

	choices, ok := chunk["choices"].([]interface{})
	require.True(t, ok)
	require.Greater(t, len(choices), index)

	choice, ok := choices[index].(map[string]interface{})
	require.True(t, ok)
	require.Contains(t, choice, "logprobs")
	assert.Nil(t, choice["logprobs"])
}

func assertStreamChoiceOmitsMessage(t *testing.T, chunk map[string]interface{}, index int) {
	t.Helper()

	choices, ok := chunk["choices"].([]interface{})
	require.True(t, ok)
	require.Greater(t, len(choices), index)

	choice, ok := choices[index].(map[string]interface{})
	require.True(t, ok)
	assert.NotContains(t, choice, "message")
}

func TestReadRequestBodyRejectsOversizedBody(t *testing.T) {
	req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader("12345"))
	rr := httptest.NewRecorder()

	body, err := readRequestBodyWithLimit(rr, req, 4)

	require.ErrorIs(t, err, errRequestBodyTooLarge)
	assert.Nil(t, body)

	writeRequestBodyError(rr, err)
	assert.Equal(t, http.StatusRequestEntityTooLarge, rr.Code)
}

func TestNewPoolRejectsNonPositiveWorkers(t *testing.T) {
	_, err := newPool(0, nil)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "workers must be positive")
}

func TestPoolAcquireHonorsCanceledContext(t *testing.T) {
	p := &pool{ch: make(chan *sdk.Client)}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	client, err := p.acquire(ctx)

	require.ErrorIs(t, err, context.Canceled)
	assert.Nil(t, client)
}
