// Package mcp provides MCP client and server implementations.
package mcp

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

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

// Client wraps an MCP client (stdio or SSE) and exposes discovered tools.
type Client struct {
	name      string
	transport string // "stdio" | "sse"
	mcpClient *client.Client
	tools     []mcp.Tool
	logger    *slog.Logger
}

// NewStdioClient creates an MCP client that connects to a stdio-based MCP server.
func NewStdioClient(ctx context.Context, name, command string, args []string, env map[string]string, logger *slog.Logger) (*Client, error) {
	logger.Info("creating stdio MCP client", "name", name, "command", command, "args", args)

	// Prepare environment
	envSlice := os.Environ()
	for k, v := range env {
		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
	}

	// Create stdio client
	mcpClient, err := client.NewStdioMCPClient(command, envSlice, args...)
	if err != nil {
		return nil, fmt.Errorf("failed to create stdio client: %w", err)
	}

	// Initialize
	initReq := mcp.InitializeRequest{
		Params: mcp.InitializeParams{
			ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
			ClientInfo: mcp.Implementation{
				Name:    "agents-mcp-client",
				Version: "1.0.0",
			},
			Capabilities: mcp.ClientCapabilities{},
		},
	}

	_, err = mcpClient.Initialize(ctx, initReq)
	if err != nil {
		mcpClient.Close()
		return nil, fmt.Errorf("failed to initialize MCP client: %w", err)
	}

	// Discover tools
	toolsResp, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
	if err != nil {
		mcpClient.Close()
		return nil, fmt.Errorf("failed to list tools: %w", err)
	}

	logger.Info("discovered MCP tools", "name", name, "count", len(toolsResp.Tools))

	return &Client{
		name:      name,
		transport: "stdio",
		mcpClient: mcpClient,
		tools:     toolsResp.Tools,
		logger:    logger,
	}, nil
}

// NewSSEClient creates an MCP client that connects to an SSE/HTTP-based MCP server.
func NewSSEClient(ctx context.Context, name, url string, headers map[string]string, logger *slog.Logger) (*Client, error) {
	logger.Info("creating SSE MCP client", "name", name, "url", url)

	// Create SSE client (no custom headers support in basic API, would need transport options)
	mcpClient, err := client.NewSSEMCPClient(url)
	if err != nil {
		return nil, fmt.Errorf("failed to create SSE client: %w", err)
	}

	// Initialize
	initReq := mcp.InitializeRequest{
		Params: mcp.InitializeParams{
			ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
			ClientInfo: mcp.Implementation{
				Name:    "agents-mcp-client",
				Version: "1.0.0",
			},
			Capabilities: mcp.ClientCapabilities{},
		},
	}

	_, err = mcpClient.Initialize(ctx, initReq)
	if err != nil {
		mcpClient.Close()
		return nil, fmt.Errorf("failed to initialize MCP client: %w", err)
	}

	// Discover tools
	toolsResp, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
	if err != nil {
		mcpClient.Close()
		return nil, fmt.Errorf("failed to list tools: %w", err)
	}

	logger.Info("discovered MCP tools", "name", name, "count", len(toolsResp.Tools))

	return &Client{
		name:      name,
		transport: "sse",
		mcpClient: mcpClient,
		tools:     toolsResp.Tools,
		logger:    logger,
	}, nil
}

// Tools returns the discovered MCP tools.
func (c *Client) Tools() []mcp.Tool {
	return c.tools
}

// Name returns the client name.
func (c *Client) Name() string {
	return c.name
}

// CallTool invokes an MCP tool by name with the given arguments.
func (c *Client) CallTool(ctx context.Context, name string, args map[string]any, timeout time.Duration) (*mcp.CallToolResult, error) {
	if timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}

	req := mcp.CallToolRequest{
		Params: mcp.CallToolParams{
			Name:      name,
			Arguments: args,
		},
	}

	result, err := c.mcpClient.CallTool(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("tool call failed: %w", err)
	}

	return result, nil
}

// Close closes the MCP client connection.
func (c *Client) Close() error {
	c.logger.Info("closing MCP client", "name", c.name, "transport", c.transport)
	return c.mcpClient.Close()
}
