/**
 * MCP Manager — lifecycle management for MCP server connections.
 *
 * Reads server configs, creates McpClients, connects them, bridges
 * their tools into the Flynn ToolRegistry, and handles shutdown.
 */

import type { ToolRegistry } from '../tools/registry.js';
import type { Tool } from '../tools/types.js';
import type { McpServerConfig, McpServerState } from './types.js';
import { McpClient } from './client.js';
import { bridgeAllTools } from './bridge.js';

export class McpManager {
  private clients: Map<string, McpClient> = new Map();
  private registeredToolNames: Map<string, string[]> = new Map(); // serverName -> prefixed tool names
  private toolRegistry: ToolRegistry;

  constructor(toolRegistry: ToolRegistry) {
    this.toolRegistry = toolRegistry;
  }

  /**
   * Start all MCP servers from config and register their tools.
   *
   * Errors on individual servers are caught and logged — one bad server
   * doesn't prevent the others from starting.
   */
  async startAll(configs: McpServerConfig[]): Promise<void> {
    const results = await Promise.allSettled(
      configs.map((config) => this.startServer(config)),
    );

    for (let i = 0; i < results.length; i++) {
      const result = results[i];
      if (result.status === 'rejected') {
        console.error(
          `MCP server '${configs[i].name}' failed to start:`,
          result.reason instanceof Error ? result.reason.message : result.reason,
        );
      }
    }
  }

  /**
   * Start a single MCP server, connect, discover tools, and register them.
   */
  async startServer(config: McpServerConfig): Promise<void> {
    // Stop existing server with same name if any
    if (this.clients.has(config.name)) {
      await this.stopServer(config.name);
    }

    const client = new McpClient(config);
    this.clients.set(config.name, client);
    this.storedConfigs.set(config.name, config);

    // Connect and discover tools
    await client.connect();

    // Bridge discovered tools into Flynn's registry
    const tools = bridgeAllTools(client);
    const toolNames: string[] = [];

    for (const tool of tools) {
      try {
        this.toolRegistry.register(tool);
        toolNames.push(tool.name);
      } catch (error) {
        console.warn(
          `Could not register MCP tool '${tool.name}':`,
          error instanceof Error ? error.message : error,
        );
      }
    }

    this.registeredToolNames.set(config.name, toolNames);

    console.log(
      `MCP server '${config.name}': ${toolNames.length} tools registered`,
    );
  }

  /**
   * Stop a single MCP server and unregister its tools.
   */
  async stopServer(name: string): Promise<void> {
    const client = this.clients.get(name);
    if (!client) {return;}

    // Unregister tools from the registry
    const toolNames = this.registeredToolNames.get(name) ?? [];
    for (const toolName of toolNames) {
      this.toolRegistry.unregister(toolName);
    }
    this.registeredToolNames.delete(name);

    // Disconnect client
    await client.disconnect();
    this.clients.delete(name);
    this.storedConfigs.delete(name);
  }

  /**
   * Stop all MCP servers and clean up.
   */
  async stopAll(): Promise<void> {
    const names = Array.from(this.clients.keys());
    await Promise.allSettled(names.map((name) => this.stopServer(name)));
  }

  /**
   * Restart a single MCP server (stop + start with same config).
   */
  async restartServer(name: string): Promise<void> {
    const client = this.clients.get(name);
    if (!client) {
      throw new Error(`MCP server '${name}' not found`);
    }

    // We need the original config to restart
    const state = this.getServerState(name);
    if (!state) {
      throw new Error(`MCP server '${name}' state not found`);
    }

    await this.stopServer(name);
    await this.startServer(state.config);
  }

  /**
   * Get the state of a specific server.
   */
  getServerState(name: string): McpServerState | undefined {
    const client = this.clients.get(name);
    if (!client) {return undefined;}

    const config = this.storedConfigs.get(name) ?? { name, command: '', args: [] };

    return {
      config,
      status: client.status,
      tools: client.tools,
      error: client.error,
    };
  }

  /**
   * List all MCP server states.
   */
  listServers(): McpServerState[] {
    return Array.from(this.clients.entries()).map(([name, client]) => ({
      config: this.storedConfigs.get(name) ?? { name, command: '', args: [] },
      status: client.status,
      tools: client.tools,
      error: client.error,
    }));
  }

  /**
   * Get all registered MCP tools (as Flynn Tool objects).
   */
  getRegisteredTools(): Tool[] {
    const tools: Tool[] = [];
    for (const toolNames of this.registeredToolNames.values()) {
      for (const name of toolNames) {
        const tool = this.toolRegistry.get(name);
        if (tool) {tools.push(tool);}
      }
    }
    return tools;
  }

  /** Stored configs for restart support and state reporting. */
  private storedConfigs: Map<string, McpServerConfig> = new Map();
}
