// Package mcptools provides bridges to convert MCP server tools into native agent tools.
package mcptools

import (
	"context"
	"fmt"
	"log/slog"
	"time"

	"github.com/mark3labs/mcp-go/mcp"

	shellmcp "github.com/enmanuel/agents/shell/mcp"
	"github.com/enmanuel/agents/tools"
)

// FromMCPServer converts tools from an MCP client into native agent tools.
// prefix is prepended to tool names to avoid collisions (e.g., "brave_" → "brave_web_search").
// filter limits which tools to expose (empty = all tools).
// timeout is the default timeout for tool calls (0 = no timeout).
func FromMCPServer(mcpClient *shellmcp.Client, prefix string, filter []string, timeout time.Duration, logger *slog.Logger) []tools.Tool {
	if timeout == 0 {
		timeout = 30 * time.Second // default timeout
	}

	mcpTools := mcpClient.Tools()
	filterSet := make(map[string]bool)
	for _, name := range filter {
		filterSet[name] = true
	}

	var result []tools.Tool
	for _, mcpTool := range mcpTools {
		// Apply filter if specified
		if len(filterSet) > 0 && !filterSet[mcpTool.Name] {
			continue
		}

		// Convert MCP tool to native tool
		toolName := prefix + mcpTool.Name
		tool := convertMCPTool(mcpClient, mcpTool, toolName, timeout, logger)
		result = append(result, tool)
	}

	logger.Info("converted MCP tools", "server", mcpClient.Name(), "count", len(result))
	return result
}

// convertMCPTool converts a single mcp.Tool to a tools.Tool.
func convertMCPTool(mcpClient *shellmcp.Client, mcpTool mcp.Tool, prefixedName string, timeout time.Duration, logger *slog.Logger) tools.Tool {
	return tools.Tool{
		Def: tools.Def{
			Name:        prefixedName,
			Description: mcpTool.Description,
			Parameters:  convertSchema(mcpTool.InputSchema),
		},
		Exec: func(ctx context.Context, args map[string]any) tools.Result {
			// Call the MCP tool (using original name without prefix)
			result, err := mcpClient.CallTool(ctx, mcpTool.Name, args, timeout)
			if err != nil {
				logger.Error("MCP tool call failed", "tool", mcpTool.Name, "error", err)
				return tools.Result{Err: err}
			}

			// Extract text from result
			output := extractTextFromResult(result)
			return tools.Result{Output: output}
		},
	}
}

// convertSchema converts an MCP InputSchema to agent tool Parameters.
func convertSchema(schema mcp.ToolInputSchema) []tools.Param {
	var params []tools.Param

	// MCP schemas are JSON Schema objects with type: "object" and properties
	if schema.Type != "object" || schema.Properties == nil {
		return params
	}

	requiredSet := make(map[string]bool)
	for _, name := range schema.Required {
		requiredSet[name] = true
	}

	for propName, propVal := range schema.Properties {
		param := tools.Param{
			Name:     propName,
			Required: requiredSet[propName],
		}

		// Extract type and description from property schema
		if propMap, ok := propVal.(map[string]any); ok {
			if typeStr, ok := propMap["type"].(string); ok {
				param.Type = typeStr
			}
			if desc, ok := propMap["description"].(string); ok {
				param.Description = desc
			}
		}

		// Default to string if type not found
		if param.Type == "" {
			param.Type = "string"
		}

		params = append(params, param)
	}

	return params
}

// extractTextFromResult extracts text content from an MCP CallToolResult.
func extractTextFromResult(result *mcp.CallToolResult) string {
	if result == nil {
		return ""
	}

	var output string
	for _, content := range result.Content {
		// Handle different content types
		switch c := content.(type) {
		case mcp.TextContent:
			output += c.Text
		case *mcp.TextContent:
			output += c.Text
		default:
			// For other content types (image, audio, resources), just indicate presence
			output += fmt.Sprintf("[non-text content: %T]\n", content)
		}
	}

	// If result has IsError flag set, prepend error indicator
	if result.IsError {
		output = "[ERROR] " + output
	}

	return output
}
