package openai

import (
	"fmt"
	"testing"

	"nex/backend/internal/conversion/canonical"

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

func TestDecodeRequest_BasicChat(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [
			{"role": "user", "content": "你好"}
		],
		"temperature": 0.7
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Equal(t, "gpt-4", req.Model)
	assert.Len(t, req.Messages, 1)
	assert.Equal(t, canonical.RoleUser, req.Messages[0].Role)
	assert.NotNil(t, req.Parameters.Temperature)
	assert.Equal(t, 0.7, *req.Parameters.Temperature)
}

func TestDecodeRequest_SystemAndDeveloper(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [
			{"role": "system", "content": "你是助手"},
			{"role": "developer", "content": "额外指令"},
			{"role": "user", "content": "你好"}
		]
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Equal(t, "你是助手\n\n额外指令", req.System)
	assert.Len(t, req.Messages, 1)
	assert.Equal(t, canonical.RoleUser, req.Messages[0].Role)
}

func TestDecodeRequest_ToolCalls(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [
			{"role": "user", "content": "天气"},
			{
				"role": "assistant",
				"tool_calls": [{
					"id": "call_123",
					"type": "function",
					"function": {"name": "get_weather", "arguments": "{\"city\":\"北京\"}"}
				}]
			}
		]
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Len(t, req.Messages, 2)
	assistantMsg := req.Messages[1]
	assert.Equal(t, canonical.RoleAssistant, assistantMsg.Role)
	found := false
	for _, b := range assistantMsg.Content {
		if b.Type == "tool_use" {
			found = true
			assert.Equal(t, "call_123", b.ID)
			assert.Equal(t, "get_weather", b.Name)
		}
	}
	assert.True(t, found)
}

func TestDecodeRequest_ToolMessage(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [
			{"role": "user", "content": "天气"},
			{
				"role": "assistant",
				"tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": "{}"}}]
			},
			{
				"role": "tool",
				"tool_call_id": "call_1",
				"content": "晴天 25°C"
			}
		]
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	toolMsg := req.Messages[2]
	assert.Equal(t, canonical.RoleTool, toolMsg.Role)
	assert.Equal(t, "call_1", toolMsg.Content[0].ToolUseID)
}

func TestDecodeRequest_MissingModel(t *testing.T) {
	body := []byte(`{"messages":[{"role":"user","content":"hi"}]}`)
	_, err := decodeRequest(body)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "INVALID_INPUT")
}

func TestDecodeRequest_MissingMessages(t *testing.T) {
	body := []byte(`{"model":"gpt-4"}`)
	_, err := decodeRequest(body)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "INVALID_INPUT")
}

func TestDecodeRequest_DeprecatedFunctions(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [{"role": "user", "content": "test"}],
		"functions": [{
			"name": "get_weather",
			"description": "获取天气",
			"parameters": {"type":"object","properties":{"city":{"type":"string"}}}
		}]
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Len(t, req.Tools, 1)
	assert.Equal(t, "get_weather", req.Tools[0].Name)
}

func TestDecodeResponse_Basic(t *testing.T) {
	body := []byte(`{
		"id": "chatcmpl-123",
		"model": "gpt-4",
		"choices": [{
			"index": 0,
			"message": {"role": "assistant", "content": "你好"},
			"finish_reason": "stop"
		}],
		"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}
	}`)

	resp, err := decodeResponse(body)
	require.NoError(t, err)
	assert.Equal(t, "chatcmpl-123", resp.ID)
	assert.Equal(t, "gpt-4", resp.Model)
	assert.Len(t, resp.Content, 1)
	assert.Equal(t, "你好", resp.Content[0].Text)
	assert.NotNil(t, resp.StopReason)
	assert.Equal(t, canonical.StopReasonEndTurn, *resp.StopReason)
	assert.Equal(t, 10, resp.Usage.InputTokens)
	assert.Equal(t, 5, resp.Usage.OutputTokens)
}

func TestDecodeResponse_ToolCalls(t *testing.T) {
	body := []byte(`{
		"id": "chatcmpl-456",
		"model": "gpt-4",
		"choices": [{
			"index": 0,
			"message": {
				"role": "assistant",
				"tool_calls": [{
					"id": "call_abc",
					"type": "function",
					"function": {"name": "search", "arguments": "{\"q\":\"test\"}"}
				}]
			},
			"finish_reason": "tool_calls"
		}]
	}`)

	resp, err := decodeResponse(body)
	require.NoError(t, err)
	found := false
	for _, b := range resp.Content {
		if b.Type == "tool_use" {
			found = true
			assert.Equal(t, "call_abc", b.ID)
			assert.Equal(t, "search", b.Name)
		}
	}
	assert.True(t, found)
	assert.Equal(t, canonical.StopReasonToolUse, *resp.StopReason)
}

func TestDecodeResponse_Thinking(t *testing.T) {
	body := []byte(`{
		"id": "chatcmpl-789",
		"model": "gpt-4",
		"choices": [{
			"index": 0,
			"message": {
				"role": "assistant",
				"content": "回答",
				"reasoning_content": "思考过程"
			},
			"finish_reason": "stop"
		}]
	}`)

	resp, err := decodeResponse(body)
	require.NoError(t, err)
	assert.Len(t, resp.Content, 2)
	assert.Equal(t, "回答", resp.Content[0].Text)
	assert.Equal(t, "thinking", resp.Content[1].Type)
	assert.Equal(t, "思考过程", resp.Content[1].Thinking)
}

func TestDecodeModelsResponse(t *testing.T) {
	body := []byte(`{
		"object": "list",
		"data": [
			{"id": "gpt-4", "object": "model", "created": 1700000000, "owned_by": "openai"},
			{"id": "gpt-3.5-turbo", "object": "model", "created": 1700000001, "owned_by": "openai"}
		]
	}`)

	list, err := decodeModelsResponse(body)
	require.NoError(t, err)
	assert.Len(t, list.Models, 2)
	assert.Equal(t, "gpt-4", list.Models[0].ID)
	assert.Equal(t, "gpt-3.5-turbo", list.Models[1].ID)
	assert.Equal(t, int64(1700000000), list.Models[0].Created)
}

func TestDecodeRequest_InvalidJSON(t *testing.T) {
	_, err := decodeRequest([]byte(`invalid json`))
	require.Error(t, err)
	assert.Contains(t, err.Error(), "JSON_PARSE_ERROR")
}

func TestDecodeRequest_Parameters(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [{"role": "user", "content": "hi"}],
		"temperature": 0.5,
		"max_completion_tokens": 2048,
		"top_p": 0.9,
		"frequency_penalty": 0.1,
		"presence_penalty": 0.2,
		"stop": ["STOP"]
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.NotNil(t, req.Parameters.Temperature)
	assert.Equal(t, 0.5, *req.Parameters.Temperature)
	assert.NotNil(t, req.Parameters.MaxTokens)
	assert.Equal(t, 2048, *req.Parameters.MaxTokens)
	assert.NotNil(t, req.Parameters.TopP)
	assert.Equal(t, 0.9, *req.Parameters.TopP)
	assert.NotNil(t, req.Parameters.FrequencyPenalty)
	assert.Equal(t, 0.1, *req.Parameters.FrequencyPenalty)
	assert.NotNil(t, req.Parameters.PresencePenalty)
	assert.Equal(t, 0.2, *req.Parameters.PresencePenalty)
	assert.Equal(t, []string{"STOP"}, req.Parameters.StopSequences)
}

func TestDecodeRequest_ToolChoice(t *testing.T) {
	tests := []struct {
		name     string
		jsonBody string
		want     *canonical.ToolChoice
	}{
		{
			name:     "auto",
			jsonBody: `{"model":"gpt-4","messages":[{"role":"user","content":"hi"}],"tool_choice":"auto"}`,
			want:     canonical.NewToolChoiceAuto(),
		},
		{
			name:     "none",
			jsonBody: `{"model":"gpt-4","messages":[{"role":"user","content":"hi"}],"tool_choice":"none"}`,
			want:     canonical.NewToolChoiceNone(),
		},
		{
			name:     "required",
			jsonBody: `{"model":"gpt-4","messages":[{"role":"user","content":"hi"}],"tool_choice":"required"}`,
			want:     canonical.NewToolChoiceAny(),
		},
		{
			name:     "named",
			jsonBody: `{"model":"gpt-4","messages":[{"role":"user","content":"hi"}],"tool_choice":{"type":"function","function":{"name":"x"}}}`,
			want:     canonical.NewToolChoiceNamed("x"),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, err := decodeRequest([]byte(tt.jsonBody))
			require.NoError(t, err)
			require.NotNil(t, req.ToolChoice)
			assert.Equal(t, tt.want.Type, req.ToolChoice.Type)
			assert.Equal(t, tt.want.Name, req.ToolChoice.Name)
		})
	}
}

func TestDecodeRequest_OutputFormat_JSONSchema(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [{"role": "user", "content": "hi"}],
		"response_format": {
			"type": "json_schema",
			"json_schema": {
				"name": "my_schema",
				"schema": {"type":"object","properties":{"name":{"type":"string"}}},
				"strict": true
			}
		}
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	require.NotNil(t, req.OutputFormat)
	assert.Equal(t, "json_schema", req.OutputFormat.Type)
	assert.Equal(t, "my_schema", req.OutputFormat.Name)
	assert.NotNil(t, req.OutputFormat.Schema)
	require.NotNil(t, req.OutputFormat.Strict)
	assert.True(t, *req.OutputFormat.Strict)
}

func TestDecodeRequest_OutputFormat_JSON(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [{"role": "user", "content": "hi"}],
		"response_format": {"type": "json_object"}
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	require.NotNil(t, req.OutputFormat)
	assert.Equal(t, "json_object", req.OutputFormat.Type)
}

func TestDecodeResponse_StopReasons(t *testing.T) {
	tests := []struct {
		name         string
		finishReason string
		want         canonical.StopReason
	}{
		{"stop→end_turn", "stop", canonical.StopReasonEndTurn},
		{"length→max_tokens", "length", canonical.StopReasonMaxTokens},
		{"tool_calls→tool_use", "tool_calls", canonical.StopReasonToolUse},
		{"content_filter→content_filter", "content_filter", canonical.StopReasonContentFilter},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			body := []byte(fmt.Sprintf(`{
				"id": "resp-1",
				"model": "gpt-4",
				"choices": [{"index": 0, "message": {"role": "assistant", "content": "ok"}, "finish_reason": "%s"}],
				"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}
			}`, tt.finishReason))

			resp, err := decodeResponse(body)
			require.NoError(t, err)
			require.NotNil(t, resp.StopReason)
			assert.Equal(t, tt.want, *resp.StopReason)
		})
	}
}

func TestDecodeResponse_Usage(t *testing.T) {
	body := []byte(`{
		"id": "resp-1",
		"model": "gpt-4",
		"choices": [{"index": 0, "message": {"role": "assistant", "content": "ok"}, "finish_reason": "stop"}],
		"usage": {
			"prompt_tokens": 100,
			"completion_tokens": 50,
			"total_tokens": 150,
			"prompt_tokens_details": {"cached_tokens": 80}
		}
	}`)

	resp, err := decodeResponse(body)
	require.NoError(t, err)
	assert.Equal(t, 100, resp.Usage.InputTokens)
	assert.Equal(t, 50, resp.Usage.OutputTokens)
	require.NotNil(t, resp.Usage.CacheReadTokens)
	assert.Equal(t, 80, *resp.Usage.CacheReadTokens)
}

func TestDecodeResponse_Refusal(t *testing.T) {
	body := []byte(`{
		"id": "resp-1",
		"model": "gpt-4",
		"choices": [{
			"index": 0,
			"message": {"role": "assistant", "content": null, "refusal": "我拒绝回答"},
			"finish_reason": "stop"
		}],
		"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}
	}`)

	resp, err := decodeResponse(body)
	require.NoError(t, err)
	found := false
	for _, b := range resp.Content {
		if b.Text == "我拒绝回答" {
			found = true
		}
	}
	assert.True(t, found)
}

func TestDecodeRequest_AssistantContentArray(t *testing.T) {
	body := []byte(`{
		"model": "gpt-4",
		"messages": [
			{"role": "user", "content": "hello"},
			{
				"role": "assistant",
				"content": [{"type": "text", "text": "hello back"}]
			}
		]
	}`)

	req, err := decodeRequest(body)
	require.NoError(t, err)
	assert.Len(t, req.Messages, 2)
	assistantMsg := req.Messages[1]
	assert.Equal(t, canonical.RoleAssistant, assistantMsg.Role)
	assert.Len(t, assistantMsg.Content, 1)
	assert.Equal(t, "text", assistantMsg.Content[0].Type)
	assert.Equal(t, "hello back", assistantMsg.Content[0].Text)
}
