package request

import (
	"encoding/json"
	"strings"

	"github.com/anthropics/anthropic-sdk-go"
	"github.com/openai/openai-go/v3"
	"github.com/openai/openai-go/v3/packages/param"
	"github.com/openai/openai-go/v3/shared"
	"github.com/tingly-dev/tingly-box/internal/protocol"
)

// ConvertAnthropicToOpenAIRequest converts Anthropic request to OpenAI format
// Returns the OpenAI request and a config object with metadata for provider transforms
func ConvertAnthropicToOpenAIRequest(anthropicReq *anthropic.MessageNewParams, compatible bool, isStreaming bool, disableStreamUsage bool) (*openai.ChatCompletionNewParams, *protocol.OpenAIConfig) {
	openaiReq := &openai.ChatCompletionNewParams{
		Model: openai.ChatModel(anthropicReq.Model),
	}

	isThinking := IsThinkingEnabled(anthropicReq)

	// Set MaxTokens
	openaiReq.MaxTokens = openai.Opt(anthropicReq.MaxTokens)

	// Convert messages
	for _, msg := range anthropicReq.Messages {
		if string(msg.Role) == "user" {
			// User messages may contain tool_result blocks - need special handling
			messages := convertAnthropicUserMessageToOpenAI(msg)
			openaiReq.Messages = append(openaiReq.Messages, messages...)
		} else if string(msg.Role) == "assistant" {
			// Convert assistant message with potential tool_use blocks
			openaiMsg := convertAnthropicAssistantMessageToOpenAI(msg)
			openaiReq.Messages = append(openaiReq.Messages, openaiMsg)
		}
	}

	// Convert system message
	if len(anthropicReq.System) > 0 {
		systemStr := ConvertTextBlocksToString(anthropicReq.System)
		systemMsg := openai.SystemMessage(systemStr)
		// Add system message at the beginning
		openaiReq.Messages = append([]openai.ChatCompletionMessageParamUnion{systemMsg}, openaiReq.Messages...)
	}

	// Convert tools from Anthropic format to OpenAI format
	if len(anthropicReq.Tools) > 0 {
		if compatible {
			openaiReq.Tools = ConvertAnthropicToolsToOpenAIWithTransformedSchema(anthropicReq.Tools)
		} else {
			openaiReq.Tools = ConvertAnthropicToolsToOpenAI(anthropicReq.Tools)
		}
		// Convert tool choice
		openaiReq.ToolChoice = ConvertAnthropicToolChoiceToOpenAI(&anthropicReq.ToolChoice)
	}

	// thinking
	config := &protocol.OpenAIConfig{
		HasThinking:     false,
		ReasoningEffort: "medium", // Default to "medium" for OpenAI-compatible APIs
	}
	if anthropicReq.Thinking.OfEnabled != nil || anthropicReq.Thinking.OfAdaptive != nil || isThinking {
		config.HasThinking = true
		config.ReasoningEffort = "medium"
	}
	if anthropicReq.OutputConfig.Effort != "" {
		config.ReasoningEffort = shared.ReasoningEffort(anthropicReq.OutputConfig.Effort)
	}

	// Only set stream_options for streaming requests (per OpenAI API spec)
	if isStreaming && !disableStreamUsage {
		openaiReq.StreamOptions.IncludeUsage = param.Opt[bool]{Value: true}
	}
	return openaiReq, config
}

// ConvertAnthropicToolsToOpenAI converts Anthropic tools to OpenAI format
func ConvertAnthropicToolsToOpenAI(tools []anthropic.ToolUnionParam) []openai.ChatCompletionToolUnionParam {
	if len(tools) == 0 {
		return nil
	}

	out := make([]openai.ChatCompletionToolUnionParam, 0, len(tools))

	for _, t := range tools {
		tool := t.OfTool
		if tool == nil {
			continue
		}

		// Convert Anthropic input schema to OpenAI function parameters
		parameters := convertAnthropicInputSchemaToOpenAIParameters(tool.InputSchema.Properties, tool.InputSchema.Required)

		// Create function with parameters
		fn := shared.FunctionDefinitionParam{
			Name:        tool.Name,
			Description: param.Opt[string]{Value: tool.Description.Value},
			Parameters:  parameters,
		}

		out = append(out, openai.ChatCompletionFunctionTool(fn))
	}

	return out
}

func convertAnthropicInputSchemaToOpenAIParameters(properties any, required []string) shared.FunctionParameters {
	if properties == nil && len(required) == 0 {
		return nil
	}

	if schema, ok := properties.(map[string]any); ok {
		if _, hasNestedProperties := schema["properties"]; hasNestedProperties {
			parameters := make(shared.FunctionParameters, len(schema)+1)
			for key, value := range schema {
				parameters[key] = value
			}
			if _, ok := parameters["type"]; !ok {
				parameters["type"] = "object"
			}
			if len(required) > 0 {
				parameters["required"] = required
			}
			return parameters
		}
	}

	parameters := shared.FunctionParameters{
		"type": "object",
	}
	if properties != nil {
		parameters["properties"] = properties
	}
	if len(required) > 0 {
		parameters["required"] = required
	}
	return parameters
}

// ConvertAnthropicToolsToOpenAIWithTransformedSchema is an alias for ConvertAnthropicToolsToOpenAI
// Schema transformation is handled by provider-specific transforms
func ConvertAnthropicToolsToOpenAIWithTransformedSchema(tools []anthropic.ToolUnionParam) []openai.ChatCompletionToolUnionParam {
	return ConvertAnthropicToolsToOpenAI(tools)
}

// ConvertAnthropicToolChoiceToOpenAI converts Anthropic tool_choice to OpenAI format
func ConvertAnthropicToolChoiceToOpenAI(tc *anthropic.ToolChoiceUnionParam) openai.ChatCompletionToolChoiceOptionUnionParam {
	if tc.OfAuto != nil {
		return openai.ChatCompletionToolChoiceOptionUnionParam{
			OfAuto: openai.Opt("auto"),
		}
	}

	if tc.OfTool != nil {
		return openai.ToolChoiceOptionFunctionToolChoice(
			openai.ChatCompletionNamedToolChoiceFunctionParam{
				Name: tc.OfTool.Name,
			},
		)
	}

	// OfAny (Anthropic's "required") - map to auto as OpenAI doesn't have direct equivalent
	// In the future, we could use OfAllowedTools with all tools listed to achieve similar behavior
	if tc.OfAny != nil {
		return openai.ChatCompletionToolChoiceOptionUnionParam{
			OfAuto: openai.Opt("auto"),
		}
	}

	// Default to auto
	return openai.ChatCompletionToolChoiceOptionUnionParam{
		OfAuto: openai.Opt("auto"),
	}
}

// convertToolResultContent extracts the content from a tool result block
// The content is a list of content blocks (typically just one text block)
func convertToolResultContent(content []anthropic.ToolResultBlockParamContentUnion) string {
	var result strings.Builder
	for _, c := range content {
		// Handle text content
		if c.OfText != nil {
			result.WriteString(c.OfText.Text)
		}
	}
	return result.String()
}

// ConvertContentBlocksToString converts Anthropic content blocks to string
func ConvertContentBlocksToString(blocks []anthropic.ContentBlockParamUnion) string {
	var result strings.Builder
	for _, block := range blocks {
		// Use the AsText helper if available, or check the type
		if block.OfText != nil {
			result.WriteString(block.OfText.Text)
		}
	}
	return result.String()
}

// imageBlockToOpenAIURL converts an Anthropic v1 image block source into the
// URL string OpenAI's image_url content part expects. Base64 sources become a
// data: URL; URL sources are passed through.
func imageBlockToOpenAIURL(img *anthropic.ImageBlockParam) string {
	if img == nil {
		return ""
	}
	if img.Source.OfBase64 != nil {
		return "data:" + string(img.Source.OfBase64.MediaType) +
			";base64," + img.Source.OfBase64.Data
	}
	if img.Source.OfURL != nil {
		return img.Source.OfURL.URL
	}
	return ""
}

// ConvertTextBlocksToString converts Anthropic TextBlockParam array to string
func ConvertTextBlocksToString(blocks []anthropic.TextBlockParam) string {
	var result strings.Builder
	for _, block := range blocks {
		result.WriteString(block.Text)
	}
	return result.String()
}

// convertAnthropicAssistantMessageToOpenAI converts Anthropic assistant message to OpenAI format
// This handles both text content and tool_use blocks
// Note: thinking content is preserved in "x_thinking" field for provider-specific transforms
func convertAnthropicAssistantMessageToOpenAI(msg anthropic.MessageParam) openai.ChatCompletionMessageParamUnion {
	var textContent string
	var toolCalls []map[string]interface{}
	var thinking string

	// Process content blocks
	for _, block := range msg.Content {
		if block.OfText != nil {
			textContent += block.OfText.Text
		} else if block.OfToolUse != nil {
			// Convert tool_use block to OpenAI tool_call format
			toolCall := map[string]interface{}{
				"id":   block.OfToolUse.ID,
				"type": "function",
				"function": map[string]interface{}{
					"name": block.OfToolUse.Name,
				},
			}
			// Marshal input to JSON string for OpenAI
			if argsBytes, err := json.Marshal(block.OfToolUse.Input); err == nil {
				toolCall["function"].(map[string]interface{})["arguments"] = string(argsBytes)
			}
			toolCalls = append(toolCalls, toolCall)
		} else if block.OfThinking != nil {
			thinking = block.OfThinking.Thinking
		}
	}

	// Build the message based on what we have
	if len(toolCalls) > 0 {
		// Use JSON marshaling to create a message with tool_calls
		msgMap := map[string]interface{}{
			"role":    "assistant",
			"content": textContent,
		}
		msgMap["tool_calls"] = toolCalls

		msgBytes, _ := json.Marshal(msgMap)
		var result openai.ChatCompletionMessageParamUnion
		_ = json.Unmarshal(msgBytes, &result)

		// Preserve x_thinking in ExtraFields for provider transforms (e.g., DeepSeek/Moonshot)
		// Must set on OfAssistant (variant level), not on union level, because
		// MarshalUnion only serializes the active variant — union-level ExtraFields are dropped.
		if result.OfAssistant != nil {
			extraFields := result.OfAssistant.ExtraFields()
			if extraFields == nil {
				extraFields = map[string]any{}
			}
			extraFields["x_thinking"] = thinking
			result.OfAssistant.SetExtraFields(extraFields)
		}

		return result
	}

	// Simple text message
	msgMap := map[string]interface{}{
		"role":    "assistant",
		"content": textContent,
	}
	msgBytes, _ := json.Marshal(msgMap)
	var result openai.ChatCompletionMessageParamUnion
	_ = json.Unmarshal(msgBytes, &result)

	// Preserve x_thinking in ExtraFields for provider transforms
	// Must set on OfAssistant (variant level), not on union level.
	if result.OfAssistant != nil {
		extraFields := result.OfAssistant.ExtraFields()
		if extraFields == nil {
			extraFields = map[string]any{}
		}
		extraFields["x_thinking"] = thinking
		result.OfAssistant.SetExtraFields(extraFields)
	}

	return result
}

// convertAnthropicUserMessageToOpenAI converts Anthropic user message to OpenAI format
// This handles text content and tool_result blocks
// tool_result blocks in Anthropic become separate role="tool" messages in OpenAI
// Returns a slice of messages because tool results become separate messages
func convertAnthropicUserMessageToOpenAI(msg anthropic.MessageParam) []openai.ChatCompletionMessageParamUnion {
	var result []openai.ChatCompletionMessageParamUnion
	var textContent string
	var hasToolResult, hasImage bool

	for _, block := range msg.Content {
		if block.OfToolResult != nil {
			hasToolResult = true
		}
		if block.OfImage != nil {
			hasImage = true
		}
	}

	// Process content blocks
	if hasToolResult {
		// When there are tool_result blocks, we need to create separate messages
		for _, block := range msg.Content {
			if block.OfText != nil {
				textContent += block.OfText.Text
			} else if block.OfToolResult != nil {
				// Convert tool_result to OpenAI role="tool" message
				// Truncate tool_call_id to meet OpenAI's 40 character limit
				truncatedID := truncateToolCallID(block.OfToolResult.ToolUseID)
				toolMsg := map[string]interface{}{
					"role":         "tool",
					"tool_call_id": truncatedID,
					"content":      convertToolResultContent(block.OfToolResult.Content),
				}
				msgBytes, _ := json.Marshal(toolMsg)
				var toolResultMsg openai.ChatCompletionMessageParamUnion
				_ = json.Unmarshal(msgBytes, &toolResultMsg)
				result = append(result, toolResultMsg)
			}
		}
		// If there was text content alongside tool results, add it as a user message
		if textContent != "" {
			result = append(result, openai.UserMessage(textContent))
		}
	} else if hasImage {
		// Multimodal user message: emit an array of text + image_url content parts
		parts := make([]map[string]interface{}, 0, len(msg.Content))
		for _, block := range msg.Content {
			switch {
			case block.OfText != nil:
				parts = append(parts, map[string]interface{}{
					"type": "text",
					"text": block.OfText.Text,
				})
			case block.OfImage != nil:
				url := imageBlockToOpenAIURL(block.OfImage)
				if url == "" {
					continue
				}
				parts = append(parts, map[string]interface{}{
					"type":      "image_url",
					"image_url": map[string]interface{}{"url": url},
				})
			}
		}
		if len(parts) > 0 {
			msgMap := map[string]interface{}{
				"role":    "user",
				"content": parts,
			}
			msgBytes, _ := json.Marshal(msgMap)
			var userMsg openai.ChatCompletionMessageParamUnion
			_ = json.Unmarshal(msgBytes, &userMsg)
			result = append(result, userMsg)
		}
	} else {
		// Simple text-only user message
		contentStr := ConvertContentBlocksToString(msg.Content)
		if contentStr != "" {
			result = append(result, openai.UserMessage(contentStr))
		}
	}

	return result
}
