package anthropic

import (
	"encoding/json"
	"testing"
	"time"

	"nex/backend/internal/conversion"
	"nex/backend/internal/conversion/canonical"

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

func TestEncodeRequest_Basic(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Stream:     true,
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages: []canonical.CanonicalMessage{
			{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}},
		},
	}
	provider := conversion.NewTargetProvider("", "key", "my-model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, "my-model", result["model"])
	assert.Equal(t, true, result["stream"])
	assert.Equal(t, float64(1024), result["max_tokens"])

	msgs, ok := result["messages"].([]any)
	require.True(t, ok)
	assert.Len(t, msgs, 1)
}

func TestEncodeRequest_ToolMergeIntoUser(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages: []canonical.CanonicalMessage{
			{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("查询")}},
			{Role: canonical.RoleAssistant, Content: []canonical.ContentBlock{canonical.NewToolUseBlock("tool_1", "search", json.RawMessage(`{"q":"test"}`))}},
			{Role: canonical.RoleTool, Content: []canonical.ContentBlock{canonical.NewToolResultBlock("tool_1", "结果", false)}},
		},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	msgs, ok := result["messages"].([]any)
	require.True(t, ok)

	// tool 消息应被合并到相邻 user 消息
	foundToolResult := false
	for _, m := range msgs {
		msgMap, ok := m.(map[string]any)
		require.True(t, ok)
		if msgMap["role"] == "user" {
			content, ok := msgMap["content"].([]any)
			if ok {
				for _, c := range content {
					block, ok := c.(map[string]any)
					require.True(t, ok)
					if block["type"] == "tool_result" {
						foundToolResult = true
					}
				}
			}
		}
	}
	assert.True(t, foundToolResult)
}

func TestEncodeRequest_FirstUserGuarantee(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages: []canonical.CanonicalMessage{
			{Role: canonical.RoleAssistant, Content: []canonical.ContentBlock{canonical.NewTextBlock("前置")}},
			{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}},
		},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	msgs, ok := result["messages"].([]any)
	require.True(t, ok)
	firstMsg, ok := msgs[0].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "user", firstMsg["role"])
}

func TestEncodeRequest_ThinkingEnabled(t *testing.T) {
	budget := 10000
	maxTokens := 8096
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
		Thinking:   &canonical.ThinkingConfig{Type: "enabled", BudgetTokens: &budget},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	thinking, ok := result["thinking"].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "enabled", thinking["type"])
	assert.Equal(t, float64(10000), thinking["budget_tokens"])
}

func TestEncodeResponse_Basic(t *testing.T) {
	sr := canonical.StopReasonEndTurn
	resp := &canonical.CanonicalResponse{
		ID:         "msg_1",
		Model:      "claude-3",
		Content:    []canonical.ContentBlock{canonical.NewTextBlock("你好")},
		StopReason: &sr,
		Usage:      canonical.CanonicalUsage{InputTokens: 10, OutputTokens: 5},
	}

	body, err := encodeResponse(resp)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, "msg_1", result["id"])
	assert.Equal(t, "message", result["type"])
	assert.Equal(t, "assistant", result["role"])
	assert.Equal(t, "end_turn", result["stop_reason"])

	content, ok := result["content"].([]any)
	require.True(t, ok)
	assert.Len(t, content, 1)
	block, ok := content[0].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "text", block["type"])
	assert.Equal(t, "你好", block["text"])
}

func TestEncodeModelsResponse(t *testing.T) {
	ts := time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC).Unix()
	list := &canonical.CanonicalModelList{
		Models: []canonical.CanonicalModel{
			{ID: "claude-3-opus", Name: "Claude 3 Opus", Created: ts, OwnedBy: "anthropic"},
		},
	}

	body, err := encodeModelsResponse(list)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	data, ok := result["data"].([]any)
	require.True(t, ok)
	assert.Len(t, data, 1)

	model, ok := data[0].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "claude-3-opus", model["id"])
	// created 应为 RFC3339 格式
	createdAt, ok := model["created_at"].(string)
	assert.True(t, ok)
	assert.Contains(t, createdAt, "2024")
}

func TestEncodeRequest_ThinkingDisabled(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	_, hasThinking := result["thinking"]
	assert.False(t, hasThinking)
}

func TestEncodeRequest_ThinkingAdaptive(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
		Thinking:   &canonical.ThinkingConfig{Type: "adaptive"},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	thinking, ok := result["thinking"].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "adaptive", thinking["type"])
}

func TestEncodeRequest_OutputFormat_JSONSchema(t *testing.T) {
	maxTokens := 1024
	schema := json.RawMessage(`{"type":"object","properties":{"name":{"type":"string"}}}`)
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
		OutputFormat: &canonical.OutputFormat{
			Type:   "json_schema",
			Schema: schema,
		},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	oc, ok := result["output_config"].(map[string]any)
	require.True(t, ok)
	format, ok := oc["format"].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "json_schema", format["type"])
	assert.NotNil(t, format["schema"])
}

func TestEncodeRequest_OutputFormat_JSON(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages:   []canonical.CanonicalMessage{{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("hi")}}},
		OutputFormat: &canonical.OutputFormat{
			Type: "json_object",
		},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	oc, ok := result["output_config"].(map[string]any)
	require.True(t, ok)
	format, ok := oc["format"].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "json_schema", format["type"])
	schemaMap, ok := format["schema"].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "object", schemaMap["type"])
}

func TestEncodeRequest_ConsecutiveRoleMerge(t *testing.T) {
	maxTokens := 1024
	req := &canonical.CanonicalRequest{
		Model:      "claude-3",
		Parameters: canonical.RequestParameters{MaxTokens: &maxTokens},
		Messages: []canonical.CanonicalMessage{
			{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("A")}},
			{Role: canonical.RoleUser, Content: []canonical.ContentBlock{canonical.NewTextBlock("B")}},
		},
	}
	provider := conversion.NewTargetProvider("", "key", "model")

	body, err := encodeRequest(req, provider)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	msgs, ok := result["messages"].([]any)
	require.True(t, ok)
	assert.Len(t, msgs, 1)
	userMsg, ok := msgs[0].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "user", userMsg["role"])
	content, ok := userMsg["content"].([]any)
	require.True(t, ok)
	assert.Len(t, content, 2)
}

func TestEncodeResponse_ContentFilter(t *testing.T) {
	sr := canonical.StopReasonContentFilter
	resp := &canonical.CanonicalResponse{
		ID:         "msg-cf",
		Model:      "claude-3",
		Content:    []canonical.ContentBlock{canonical.NewTextBlock("hi")},
		StopReason: &sr,
	}

	body, err := encodeResponse(resp)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	assert.Equal(t, "end_turn", result["stop_reason"])
}

func TestEncodeResponse_ReasoningTokens(t *testing.T) {
	reasoning := 100
	sr := canonical.StopReasonEndTurn
	resp := &canonical.CanonicalResponse{
		ID:         "msg-rt",
		Model:      "claude-3",
		Content:    []canonical.ContentBlock{canonical.NewTextBlock("hi")},
		StopReason: &sr,
		Usage:      canonical.CanonicalUsage{InputTokens: 10, OutputTokens: 5, ReasoningTokens: &reasoning},
	}

	body, err := encodeResponse(resp)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	usage, ok := result["usage"].(map[string]any)
	require.True(t, ok)
	_, hasReasoning := usage["reasoning_tokens"]
	assert.False(t, hasReasoning)
}

func TestEncodeResponse_ToolUse(t *testing.T) {
	sr := canonical.StopReasonToolUse
	input := json.RawMessage(`{"q":"test"}`)
	resp := &canonical.CanonicalResponse{
		ID:         "msg-tool",
		Model:      "claude-3",
		Content:    []canonical.ContentBlock{canonical.NewToolUseBlock("tool_1", "search", input)},
		StopReason: &sr,
	}

	body, err := encodeResponse(resp)
	require.NoError(t, err)

	var result map[string]any
	require.NoError(t, json.Unmarshal(body, &result))
	content, ok := result["content"].([]any)
	require.True(t, ok)
	assert.Len(t, content, 1)
	block, ok := content[0].(map[string]any)
	require.True(t, ok)
	assert.Equal(t, "tool_use", block["type"])
	assert.Equal(t, "tool_1", block["id"])
	assert.Equal(t, "search", block["name"])
}
