package mcp

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

	"github.com/enmanuel/agents/internal/config"
)

// Manager manages multiple MCP client connections.
type Manager struct {
	clients map[string]*Client // server name → client
	logger  *slog.Logger
}

// NewManager creates a new MCP manager and initializes all configured servers.
func NewManager(ctx context.Context, servers []config.MCPServerCfg, logger *slog.Logger) (*Manager, error) {
	if logger == nil {
		logger = slog.Default()
	}

	m := &Manager{
		clients: make(map[string]*Client),
		logger:  logger,
	}

	for _, serverCfg := range servers {
		if err := m.addServer(ctx, serverCfg); err != nil {
			// Close any already-created clients before returning error
			m.Close()
			return nil, fmt.Errorf("failed to initialize MCP server %q: %w", serverCfg.Name, err)
		}
	}

	logger.Info("MCP manager initialized", "servers", len(m.clients))
	return m, nil
}

// addServer creates and adds a single MCP client to the manager.
func (m *Manager) addServer(ctx context.Context, cfg config.MCPServerCfg) error {
	if cfg.Name == "" {
		return fmt.Errorf("MCP server must have a name")
	}

	// Auto-detect transport if not specified
	transport := cfg.Transport
	if transport == "" {
		if cfg.Command != "" {
			transport = "stdio"
		} else if cfg.URL != "" {
			transport = "sse"
		} else {
			return fmt.Errorf("MCP server %q must specify either command (stdio) or url (sse)", cfg.Name)
		}
	}

	var client *Client
	var err error

	switch transport {
	case "stdio":
		if cfg.Command == "" {
			return fmt.Errorf("MCP server %q with stdio transport must have a command", cfg.Name)
		}
		// Expand environment variables in command and args
		command := os.ExpandEnv(cfg.Command)
		args := make([]string, len(cfg.Args))
		for i, arg := range cfg.Args {
			args[i] = os.ExpandEnv(arg)
		}
		// Expand env vars in environment map
		env := make(map[string]string, len(cfg.Env))
		for k, v := range cfg.Env {
			env[k] = os.ExpandEnv(v)
		}
		client, err = NewStdioClient(ctx, cfg.Name, command, args, env, m.logger)

	case "sse":
		if cfg.URL == "" {
			return fmt.Errorf("MCP server %q with sse transport must have a url", cfg.Name)
		}
		url := os.ExpandEnv(cfg.URL)
		headers := make(map[string]string, len(cfg.Headers))
		for k, v := range cfg.Headers {
			headers[k] = os.ExpandEnv(v)
		}
		client, err = NewSSEClient(ctx, cfg.Name, url, headers, m.logger)

	default:
		return fmt.Errorf("unknown transport %q for MCP server %q (must be stdio or sse)", transport, cfg.Name)
	}

	if err != nil {
		return err
	}

	m.clients[cfg.Name] = client
	m.logger.Info("MCP server connected", "name", cfg.Name, "transport", transport, "tools", len(client.Tools()))
	return nil
}

// GetClient returns an MCP client by name, or nil if not found.
func (m *Manager) GetClient(name string) *Client {
	return m.clients[name]
}

// AllClients returns all MCP clients managed by this manager.
func (m *Manager) AllClients() map[string]*Client {
	return m.clients
}

// Close closes all MCP client connections.
func (m *Manager) Close() error {
	var errs []string
	for name, client := range m.clients {
		if err := client.Close(); err != nil {
			errs = append(errs, fmt.Sprintf("%s: %v", name, err))
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("errors closing MCP clients: %s", strings.Join(errs, "; "))
	}

	m.logger.Info("MCP manager closed", "servers", len(m.clients))
	return nil
}
