package anthropic_test

import (
	gocontext "context"
	"encoding/json"
	"strings"
	"testing"

	"github.com/yaoapp/gou/connector"
	goullm "github.com/yaoapp/gou/llm"
	"github.com/yaoapp/yao/agent/context"
	"github.com/yaoapp/yao/agent/llm"
	"github.com/yaoapp/yao/agent/output/message"
	"github.com/yaoapp/yao/config"
	"github.com/yaoapp/yao/openapi/oauth/types"
	"github.com/yaoapp/yao/test"
)

// testConnectorID uses the cheapest model (Claude Haiku 4.5) to save tokens
const testConnectorID = "claude.haiku-3_0"

// TestAnthropicStreamBasic tests basic streaming completion with Anthropic API
func TestAnthropicStreamBasic(t *testing.T) {
	test.Prepare(t, config.Conf)
	defer test.Clean()

	conn, err := connector.Select(testConnectorID)
	if err != nil {
		t.Fatalf("Failed to select connector: %v", err)
	}

	// Verify it's an Anthropic connector
	if !conn.Is(connector.ANTHROPIC) {
		t.Fatal("Connector is not ANTHROPIC type")
	}

	options := &context.CompletionOptions{
		Capabilities: &goullm.Capabilities{
			Streaming: true,
			ToolCalls: true,
		},
	}

	llmInstance, err := llm.New(conn, options)
	if err != nil {
		t.Fatalf("Failed to create LLM instance: %v", err)
	}

	messages := []context.Message{
		{
			Role:    context.RoleUser,
			Content: "Say 'Hi' in one word.",
		},
	}

	maxTokens := 10
	options.MaxTokens = &maxTokens

	ctx := newTestContext("test-anthropic-stream", testConnectorID)

	var chunks []string
	handler := func(chunkType message.StreamChunkType, data []byte) int {
		chunks = append(chunks, string(data))
		t.Logf("Stream chunk [%s]: %s", chunkType, string(data))
		return 0
	}

	response, err := llmInstance.Stream(ctx, messages, options, handler)
	if err != nil {
		t.Fatalf("Stream failed: %v", err)
	}

	if response == nil {
		t.Fatal("Response is nil")
	}
	if response.ID == "" {
		t.Error("Response ID is empty")
	}
	if response.Model == "" {
		t.Error("Response Model is empty")
	}
	if response.Content == "" {
		t.Error("Response content is empty")
	}
	if response.FinishReason == "" {
		t.Error("FinishReason is empty")
	}
	if response.Usage == nil {
		t.Error("Response Usage is nil")
	} else {
		t.Logf("Usage: prompt=%d, completion=%d, total=%d",
			response.Usage.PromptTokens, response.Usage.CompletionTokens, response.Usage.TotalTokens)
	}
	if len(chunks) == 0 {
		t.Error("No streaming chunks received")
	}

	t.Logf("Final response content: %s", response.Content)
	t.Logf("Total chunks received: %d", len(chunks))
}

// TestAnthropicStreamWithToolCalls tests streaming with tool calls via Anthropic API
func TestAnthropicStreamWithToolCalls(t *testing.T) {
	test.Prepare(t, config.Conf)
	defer test.Clean()

	conn, err := connector.Select(testConnectorID)
	if err != nil {
		t.Fatalf("Failed to select connector: %v", err)
	}

	options := &context.CompletionOptions{
		Capabilities: &goullm.Capabilities{
			Streaming: true,
			ToolCalls: true,
		},
	}

	weatherTool := map[string]interface{}{
		"type": "function",
		"function": map[string]interface{}{
			"name":        "get_weather",
			"description": "Get the current weather for a location",
			"parameters": map[string]interface{}{
				"type": "object",
				"properties": map[string]interface{}{
					"location": map[string]interface{}{
						"type":        "string",
						"description": "The city name, e.g. Tokyo",
					},
				},
				"required": []string{"location"},
			},
		},
	}

	options.Tools = []map[string]interface{}{weatherTool}
	options.ToolChoice = "auto"

	llmInstance, err := llm.New(conn, options)
	if err != nil {
		t.Fatalf("Failed to create LLM instance: %v", err)
	}

	messages := []context.Message{
		{
			Role:    context.RoleUser,
			Content: "What's the weather in Tokyo?",
		},
	}

	ctx := newTestContext("test-anthropic-tool", testConnectorID)

	var toolCallChunks int
	handler := func(chunkType message.StreamChunkType, data []byte) int {
		if chunkType == message.ChunkToolCall {
			toolCallChunks++
		}
		t.Logf("Stream chunk [%s]: %s", chunkType, string(data))
		return 0
	}

	response, err := llmInstance.Stream(ctx, messages, options, handler)
	if err != nil {
		t.Fatalf("Stream with tool calls failed: %v", err)
	}

	if response == nil {
		t.Fatal("Response is nil")
	}

	if len(response.ToolCalls) == 0 {
		t.Error("Expected tool calls but got none")
	} else {
		t.Logf("Received %d tool call(s)", len(response.ToolCalls))
		for i, tc := range response.ToolCalls {
			t.Logf("Tool call %d: %s(%s)", i, tc.Function.Name, tc.Function.Arguments)

			if tc.ID == "" {
				t.Errorf("Tool call %d missing ID", i)
			}
			if tc.Function.Name == "" {
				t.Errorf("Tool call %d missing function name", i)
			}
			if tc.Function.Name != "get_weather" {
				t.Errorf("Tool call %d expected 'get_weather', got '%s'", i, tc.Function.Name)
			}
			if tc.Function.Arguments == "" {
				t.Errorf("Tool call %d missing arguments", i)
			}

			// Verify arguments contain location
			var args map[string]interface{}
			if err := json.Unmarshal([]byte(tc.Function.Arguments), &args); err == nil {
				if _, hasLocation := args["location"]; !hasLocation {
					t.Errorf("Tool call %d arguments missing 'location'", i)
				}
			}
		}
	}

	if response.FinishReason != context.FinishReasonToolCalls {
		t.Logf("Warning: Expected finish_reason='tool_calls', got '%s'", response.FinishReason)
	}

	t.Logf("Final response: %+v", response)
}

// TestAnthropicStreamRetry tests error handling with invalid API key
func TestAnthropicStreamRetry(t *testing.T) {
	test.Prepare(t, config.Conf)
	defer test.Clean()

	connDSL := `{
		"type": "anthropic",
		"options": {
			"model": "claude-haiku-4-5-20251001",
			"key": "sk-ant-invalid-key-should-fail"
		}
	}`

	conn, err := connector.New("anthropic", "test-anthropic-retry", []byte(connDSL))
	if err != nil {
		t.Fatalf("Failed to create test connector: %v", err)
	}

	options := &context.CompletionOptions{
		Capabilities: &goullm.Capabilities{
			Streaming: true,
			ToolCalls: true,
		},
	}

	llmInstance, err := llm.New(conn, options)
	if err != nil {
		t.Fatalf("Failed to create LLM instance: %v", err)
	}

	messages := []context.Message{
		{
			Role:    context.RoleUser,
			Content: "Test",
		},
	}

	ctx := newTestContext("test-anthropic-retry", "test-anthropic-retry")

	_, err = llmInstance.Stream(ctx, messages, options, nil)
	if err == nil {
		t.Fatal("Expected error due to invalid API key, but got success")
	}

	errMsg := strings.ToLower(err.Error())
	hasExpectedError := strings.Contains(errMsg, "401") ||
		strings.Contains(errMsg, "authentication") ||
		strings.Contains(errMsg, "invalid") ||
		strings.Contains(errMsg, "no data received")

	if !hasExpectedError {
		t.Errorf("Expected authentication error, got: %v", err)
	}

	t.Logf("Failed as expected with error: %v", err)
}

// ============================================================================
// Helper Functions
// ============================================================================

func newTestContext(chatID, connectorID string) *context.Context {
	authorized := &types.AuthorizedInfo{
		Subject:   "test-user",
		ClientID:  "test-client",
		UserID:    "test-user-123",
		TeamID:    "test-team-456",
		TenantID:  "test-tenant-789",
		SessionID: "test-session-id",
		Constraints: types.DataConstraints{
			TeamOnly: true,
			Extra: map[string]interface{}{
				"test": "anthropic-provider",
			},
		},
	}

	ctx := context.New(gocontext.Background(), authorized, chatID)
	ctx.AssistantID = "test-assistant"
	ctx.Locale = "en-us"
	ctx.Theme = "light"
	ctx.Client = context.Client{
		Type:      "web",
		UserAgent: "AnthropicProviderTest/1.0",
		IP:        "127.0.0.1",
	}
	ctx.Referer = context.RefererAPI
	ctx.Accept = context.AcceptStandard
	ctx.Route = "/api/test"
	ctx.Metadata = make(map[string]interface{})
	return ctx
}
