package claude

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	agentContext "github.com/yaoapp/yao/agent/context"
)

func TestBuildCommand(t *testing.T) {
	messages := []agentContext.Message{
		{Role: "system", Content: "You are a helpful assistant"},
		{Role: "user", Content: "Hello"},
	}

	opts := &Options{
		ConnectorHost: "https://api.example.com",
		ConnectorKey:  "key123",
		Model:         "test-model",
	}

	cmd, env, err := BuildCommand(messages, opts)
	require.NoError(t, err)

	// Verify command structure
	// Command is now: ["bash", "-c", "cat << 'INPUTEOF' | claude -p ... INPUTEOF"]
	assert.Equal(t, "bash", cmd[0])
	assert.Equal(t, "-c", cmd[1])
	// User message should be in bash command (as JSONL via stdin)
	assert.Contains(t, cmd[2], "Hello")
	// Should have stream-json flags
	assert.Contains(t, cmd[2], "--input-format")
	assert.Contains(t, cmd[2], "--output-format")
	assert.Contains(t, cmd[2], "--include-partial-messages")
	assert.Contains(t, cmd[2], "--verbose")
	assert.Contains(t, cmd[2], "stream-json")

	// Verify environment variables (claude-proxy)
	assert.Equal(t, "http://127.0.0.1:3456", env["ANTHROPIC_BASE_URL"])
	assert.Equal(t, "dummy", env["ANTHROPIC_API_KEY"])
}

func TestBuildCommandWithSystemPrompt(t *testing.T) {
	messages := []agentContext.Message{
		{Role: "system", Content: "You are a code reviewer"},
		{Role: "user", Content: "Review this code"},
		{Role: "assistant", Content: "Sure, I'll review it"},
		{Role: "user", Content: "Here is the code"},
	}

	opts := &Options{}

	cmd, _, err := BuildCommand(messages, opts)
	require.NoError(t, err)

	// System prompt should be written to file via heredoc, then passed via --append-system-prompt-file
	bashCmd := cmd[2] // The bash -c command string
	assert.Contains(t, bashCmd, "cat << 'PROMPTEOF' > /tmp/.system-prompt.txt")
	assert.Contains(t, bashCmd, "You are a code reviewer")
	assert.Contains(t, bashCmd, "PROMPTEOF")
	assert.Contains(t, bashCmd, "--append-system-prompt-file")
	assert.Contains(t, bashCmd, "/tmp/.system-prompt.txt")
}

func TestBuildCommandWithSpecialCharsInPrompt(t *testing.T) {
	// Test that special characters in prompts are handled correctly
	messages := []agentContext.Message{
		{Role: "system", Content: "You are a helper.\n\n## Rules\n- Rule 1: Don't use \"quotes\" wrongly\n- Rule 2: Handle 'single quotes' too\n- Rule 3: Special chars like $VAR and `backticks`"},
		{Role: "user", Content: "Hello"},
	}

	opts := &Options{}

	cmd, _, err := BuildCommand(messages, opts)
	require.NoError(t, err)

	bashCmd := cmd[2]
	// The heredoc approach should preserve all special characters
	assert.Contains(t, bashCmd, "## Rules")
	assert.Contains(t, bashCmd, `Don't use "quotes" wrongly`)
	assert.Contains(t, bashCmd, "'single quotes'")
}

func TestBuildCommandWithArguments(t *testing.T) {
	messages := []agentContext.Message{
		{Role: "user", Content: "Hello"},
	}

	opts := &Options{
		Arguments: map[string]interface{}{
			"max_turns":       20,
			"permission_mode": "acceptEdits",
		},
	}

	cmd, _, err := BuildCommand(messages, opts)
	require.NoError(t, err)

	bashCmd := cmd[2] // The bash -c command string
	// max_turns should be in command args via --max-turns
	assert.Contains(t, bashCmd, "--max-turns")
	assert.Contains(t, bashCmd, "20")
	// permission_mode should be in command args
	assert.Contains(t, bashCmd, "acceptEdits")
}

func TestBuildProxyConfig(t *testing.T) {
	opts := &Options{
		ConnectorHost: "https://api.example.com",
		ConnectorKey:  "key123",
		Model:         "test-model",
	}

	configJSON, err := BuildProxyConfig(opts)
	require.NoError(t, err)

	configStr := string(configJSON)
	// Proxy config uses simple format
	// BuildAPIURL adds /v1 prefix for hosts that don't end with "/"
	assert.Contains(t, configStr, "backend")
	assert.Contains(t, configStr, "https://api.example.com/v1/chat/completions")
	assert.Contains(t, configStr, "api_key")
	assert.Contains(t, configStr, "key123")
	assert.Contains(t, configStr, "model")
	assert.Contains(t, configStr, "test-model")
}

func TestBuildProxyConfigVolcengine(t *testing.T) {
	opts := &Options{
		ConnectorHost: "https://ark.cn-beijing.volces.com/api/v3/",
		ConnectorKey:  "test-key",
		Model:         "ep-xxx",
	}

	configJSON, err := BuildProxyConfig(opts)
	require.NoError(t, err)

	configStr := string(configJSON)
	// URL should end with /chat/completions
	assert.Contains(t, configStr, "/chat/completions")
	assert.Contains(t, configStr, "ep-xxx")
}

func TestBuildInputJSONL(t *testing.T) {
	messages := []agentContext.Message{
		{Role: "system", Content: "You are helpful"},
		{Role: "user", Content: "Hello"},
		{Role: "assistant", Content: "Hi there!"},
		{Role: "user", Content: "How are you?"},
	}

	jsonl, err := BuildInputJSONL(messages)
	require.NoError(t, err)

	// Should not contain system messages (handled separately)
	assert.NotContains(t, string(jsonl), "You are helpful")

	// Should contain user and assistant messages
	assert.Contains(t, string(jsonl), "Hello")
	assert.Contains(t, string(jsonl), "Hi there!")
	assert.Contains(t, string(jsonl), "How are you?")

	// Verify JSONL format (each line is valid JSON)
	lines := splitLines(string(jsonl))
	for _, line := range lines {
		if line == "" {
			continue
		}
		var msg map[string]interface{}
		err := json.Unmarshal([]byte(line), &msg)
		assert.NoError(t, err, "Line should be valid JSON: %s", line)
		assert.Contains(t, msg, "type")
		assert.Contains(t, msg, "message")
	}
}

func TestBuildInputJSONLMultimodal(t *testing.T) {
	// Test with multimodal content (image)
	messages := []agentContext.Message{
		{
			Role: "user",
			Content: []interface{}{
				map[string]interface{}{"type": "text", "text": "What's in this image?"},
				map[string]interface{}{
					"type": "image",
					"source": map[string]interface{}{
						"type":       "base64",
						"media_type": "image/png",
						"data":       "iVBORw0KGgo=",
					},
				},
			},
		},
	}

	jsonl, err := BuildInputJSONL(messages)
	require.NoError(t, err)

	// Should contain the multimodal content
	assert.Contains(t, string(jsonl), "What's in this image?")
	assert.Contains(t, string(jsonl), "image")
	assert.Contains(t, string(jsonl), "base64")
}

func TestGetMessageContent(t *testing.T) {
	// String content
	msg1 := agentContext.Message{Content: "Hello World"}
	assert.Equal(t, "Hello World", getMessageContent(msg1))

	// Nil content
	msg2 := agentContext.Message{Content: nil}
	assert.Equal(t, "", getMessageContent(msg2))

	// Array content (multimodal)
	msg3 := agentContext.Message{
		Content: []interface{}{
			map[string]interface{}{"type": "text", "text": "Part 1"},
			map[string]interface{}{"type": "text", "text": "Part 2"},
		},
	}
	assert.Contains(t, getMessageContent(msg3), "Part 1")
	assert.Contains(t, getMessageContent(msg3), "Part 2")
}

// Helper to split lines
func splitLines(s string) []string {
	var lines []string
	start := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '\n' {
			lines = append(lines, s[start:i])
			start = i + 1
		}
	}
	if start < len(s) {
		lines = append(lines, s[start:])
	}
	return lines
}
