package server

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	"github.com/scaleway/scaleway-cli/v2/core"
)

// MCPServer wraps the MCP server with CLI command integration
type MCPServer struct {
	server    *mcp.Server
	commands  []*CommandTool
	resources []*CommandResource
}

// NewMCPServer creates a new MCP server that exposes CLI commands as tools and resources
// Meta should be injected into the context by callers before tool/resource execution.
func NewMCPServer(
	commands []*CommandTool,
	buildInfo core.BuildInfo,
) *MCPServer {
	versionStr := ""
	if buildInfo.Version != nil {
		versionStr = buildInfo.Version.String()
	}

	mcpServer := mcp.NewServer(&mcp.Implementation{
		Name:       "scaleway-mcp",
		Title:      "Scaleway MCP Server",
		WebsiteURL: "https://cli.scaleway.com",
		Version:    versionStr,
		Icons: []mcp.Icon{
			{
				Source:   "https://raw.githubusercontent.com/scaleway/scaleway-cli/main/docs/static_files/cli-artwork.png",
				MIMEType: "image/png",
				Sizes:    []string{"48x48"},
			},
		},
	}, &mcp.ServerOptions{
		// Enable tools and resources capabilities with listChanged notifications
		Capabilities: &mcp.ServerCapabilities{
			Tools:     &mcp.ToolCapabilities{ListChanged: true},
			Resources: &mcp.ResourceCapabilities{ListChanged: true},
		},
	})

	s := &MCPServer{
		server:    mcpServer,
		commands:  commands,
		resources: make([]*CommandResource, 0),
	}

	// Register all commands during initialization
	for _, cmd := range commands {
		// Register as a tool
		mcpTool := cmd.ToMCPTool()

		// Create a wrapper function for the tool using the correct MCP SDK signature
		wrapper := func(ctx context.Context, req *mcp.CallToolRequest, input map[string]any) (*mcp.CallToolResult, map[string]any, error) {
			result, err := cmd.Execute(ctx, input)
			// Initialize output map to avoid nil serialization issues
			output := map[string]any{}
			if err != nil {
				// Return error - MCP SDK will wrap it
				output["error"] = err.Error()

				return result, output, err
			}
			// Extract text content for structured output
			if len(result.Content) > 0 {
				if tc, ok := result.Content[0].(*mcp.TextContent); ok {
					output["result"] = tc.Text
				} else {
					// Handle non-TextContent types by marshaling to JSON
					if data, marshalErr := json.Marshal(result.Content[0]); marshalErr == nil {
						output["result"] = string(data)
					} else {
						output["result"] = fmt.Sprintf("%T", result.Content[0])
					}
				}
			} else {
				output["result"] = "Command executed successfully (no output)"
			}

			return result, output, nil
		}

		// Register with MCP SDK
		mcp.AddTool(mcpServer, mcpTool, wrapper)

		// Register as a resource if it's a list command
		if cmd.Command.IsList() {
			if err := s.LoadResource(cmd.Command); err != nil {
				// Log but don't fail - some commands might not be compatible
				log.Printf(
					"Warning: failed to load resource %s: %v\n",
					cmd.Command.GetCommandLine("scw"),
					err,
				)
			}
		}
	}

	return s
}

// Run starts the MCP server using the specified transport
func (s *MCPServer) Run(ctx context.Context, transport mcp.Transport) error {
	return s.server.Run(ctx, transport)
}

// Server returns the underlying MCP server for direct transport connections
func (s *MCPServer) Server() *mcp.Server {
	return s.server
}

// RegisteredCommands returns the list of commands registered as MCP tools
func (s *MCPServer) RegisteredCommands() []*CommandTool {
	return s.commands
}

// RegisteredResources returns the list of commands registered as MCP resources
func (s *MCPServer) RegisteredResources() []*CommandResource {
	return s.resources
}

// Serve runs the MCP server with the specified transport.
// It handles graceful shutdown and transport selection.
func (s *MCPServer) Serve(
	ctx context.Context,
	transportMode string,
	address string,
) (any, error) {
	fmt.Fprintf(os.Stderr, "%d registered commands\n", len(s.RegisteredCommands()))
	fmt.Fprintf(os.Stderr, "%d registered resources\n", len(s.RegisteredResources()))
	// Log transport information
	fmt.Fprintf(os.Stderr, "Running MCP server with %s transport", transportMode)
	if transportMode != "stdio" {
		fmt.Fprintf(os.Stderr, " on %s", address)
	}
	fmt.Fprintf(os.Stderr, "\n")

	// Setup graceful shutdown
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-sigChan
		cancel()
	}()

	// Run server based on transport mode
	switch transportMode {
	case "stdio":
		if err := s.Run(ctx, &mcp.StdioTransport{}); err != nil {
			return nil, fmt.Errorf("MCP server error: %w", err)
		}

	case "streamable-http":
		return nil, RunStreamableHTTPServer(ctx, s, address)

	default:
		return nil, fmt.Errorf(
			"unknown transport mode: %s (valid modes: stdio, streamable-http)",
			transportMode,
		)
	}

	return map[string]string{"status": "shutdown"}, nil
}
