#!/usr/bin/env node

/**
 * MCP Server for SAP HANA CLI
 *
 * CRITICAL: MCP communicates via JSON-RPC over STDIO. All logging MUST use
 * console.error() (stderr), never console.log() (stdout). Any non-JSON output
 * to stdout will break the protocol.
 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { pathToFileURL } from 'url';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { readFileSync } from 'fs';
import { listResources, readResource } from './resources.js';
import { listPrompts, getPrompt } from './prompts.js';
import { getDiscoveryToolDefinitions, handleDiscoveryTool } from './tools/discovery-tools.js';
import { getContentToolDefinitions, handleContentTool } from './tools/content-tools.js';
import { getSearchToolDefinitions, handleSearchTool } from './tools/search-tools.js';
import { initCliTools, getCliToolDefinitions, getAllCliToolDefinitions, getCliToolDefinitionsForCategory, handleCliTool } from './tools/cli-tools.js';
import { initRouterTool, getRouterToolDefinition, handleRouterTool } from './tools/router-tool.js';
import { isRouterTool, MAX_DYNAMIC_TOOLS } from './tools/tier-config.js';
import { errorResponse } from './tools/types.js';
import type { ToolDefinition } from './tools/types.js';

const fullMode = process.argv.includes('--full');

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'mcp-server', 'package.json'), 'utf-8')) as {
  version: string;
};

class HanaCliMcpServer {
  private server: Server;
  private commands: Map<string, any> = new Map();
  private activatedCategories: string[] = [];
  private dynamicToolDefinitions: ToolDefinition[] = [];

  constructor() {
    const iconPngDataUri =
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAM0SURBVFhHxZLfT5JhFMf5E/wPdCRkhWLXXXWvN93VHRVG1szNdVFtaUUta5mhVFKrtF+WAgFmzkz7AWrJfFADMU0gimyllGnLuvg2nhce3vd9rFgXb2f7jHO+5znnfAeoAKj+J5ygNJygNJygNJygNJygNJygNJygNCqN+z0yFLr4vNCVEGmJNOJcINsXz4o14VOiu+eIquBODCny0580b8/mf+rlt0d/+06ip9+JZ9V3YqSgM56nym+dRoY9XXFKpSfOtPzWGVE+jQJZLfRX06S5eE7dNkMKbszm0Z9A3RLCJe8HJJd+Qh4D4S9QtwSx6UpY0m/oT2CNLYTKzlnJ+0xMJr6h0h5BandqXm0LMQptk0R9OUyPUwNHPFH5PItHEwvQnAvgiDsi0UPxJWgtAZhuvZLo4kh+/YHyy0FoLAH6NoWmaYxorePsODXQF/jEhm48SWBbyzhqbobhHJpDuTUAzakXeD71WbI8FWXNAZiuhVjtHJ6j9XA4ybSLvXE6rz01grWnR4jmjF9ynBroHf3IBhYWV9DW/xblZ/1Yax6E1jyIsgY/64uj9VEcu2zjrD7fHYH22CBqWrOmLnRH6Y4i8xApOjHMHacGjNZRNiAO38t5bG30o74j+zU3u16zPPp+GRXnCasHg/Owemapnon6u1PQHn5KNHXeVY9TA+sO9MPY+ALj0/zXHEssUzJRZvaiZyjB6mbnNMvlsfBlBZvrnpL1hx7/9jg1oKvpRYatx7246pmR72LhG/uI6LslVovzTCx8XkFHXwxbjj4jG/Y//ONxwUBVN13U1D4J40kfqs8+l+/MKSy3Q9BVPRCo7iG66p6/HqcGDjaPyHetGs/8c4z55Hd5G5abQegqu1C8p4vo9t7P6Tg1sK/eh7HQvHwfIm8WJXnxLjdKTAInbPwf13J9AsUmDynZ7cn5ODWgN95DBmPtAHbWDtC8RKRLqHAJ8G9ISYUrT2+wl5YaHDmbUG3c4USK0hTbHUK+3ZEl3WM6rbP99CzR73TmfFRiILVEb7ALCw0CrKY9kRlRznSDg+gN9n86Tg3IBaXhBKXhBKXhBKXhBKXhBKXhBKXhBKX5BUtWC46LA416AAAAAElFTkSuQmCC';

    this.server = new Server(
      {
        name: 'hana-cli-mcp-server',
        version: pkg.version,
        icons: [
          {
            src: iconPngDataUri,
            mimeType: 'image/png',
            sizes: ['any'],
          },
        ],
      },
      {
        capabilities: {
          tools: { listChanged: true },
          resources: {},
          prompts: {},
        },
      }
    );

    this.setupHandlers();
    this.setupErrorHandling();
  }

  private setupErrorHandling(): void {
    this.server.onerror = (error) => {
      console.error('[MCP Error]', error);
    };

    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  private setupHandlers(): void {
    this.setupResourceHandlers();
    this.setupPromptHandlers();

    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      if (fullMode) {
        const tools = [
          ...getDiscoveryToolDefinitions(),
          ...getContentToolDefinitions(),
          ...getSearchToolDefinitions(),
          ...getAllCliToolDefinitions(),
        ];
        return { tools };
      }

      const tools = [
        ...getDiscoveryToolDefinitions(),
        ...getContentToolDefinitions(),
        ...getSearchToolDefinitions(),
        getRouterToolDefinition(),
        ...getCliToolDefinitions(),
        ...this.dynamicToolDefinitions,
      ];
      return { tools };
    });

    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
      const commandName = name.startsWith('hana_') ? name.slice(5) : name;

      if (isRouterTool(name)) {
        const result = await handleRouterTool(args as Record<string, any>);
        if (result) return result;
      }

      const discoveryOptions = fullMode ? undefined : {
        onCategoryActivated: (category: string) => this.activateCategory(category),
      };

      const result =
        handleDiscoveryTool(commandName, args as Record<string, any>, discoveryOptions) ??
        handleContentTool(commandName, args as Record<string, any>) ??
        handleSearchTool(commandName, args as Record<string, any>) ??
        await handleCliTool(name, args as Record<string, any>);

      if (result) return result;

      return errorResponse(`Unknown tool: ${name}`);
    });
  }

  private setupResourceHandlers(): void {
    this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
      try {
        return { resources: listResources() };
      } catch (error) {
        console.error('[MCP] Error listing resources:', error);
        return { resources: [] };
      }
    });

    this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
      try {
        const content = await readResource(request.params.uri);
        return { contents: [content] };
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        console.error(`[MCP] Error reading resource ${request.params.uri}:`, errorMsg);
        throw new Error(`Failed to read resource: ${errorMsg}`);
      }
    });
  }

  private setupPromptHandlers(): void {
    this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
      try {
        return { prompts: listPrompts() };
      } catch (error) {
        console.error('[MCP] Error listing prompts:', error);
        return { prompts: [] };
      }
    });

    this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
      try {
        const { name, arguments: args } = request.params;
        return getPrompt(name, args as Record<string, string> | undefined);
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        console.error(`[MCP] Error getting prompt ${request.params.name}:`, errorMsg);
        throw new Error(`Failed to get prompt: ${errorMsg}`);
      }
    });
  }

  private activateCategory(category: string): void {
    if (this.activatedCategories.includes(category)) return;

    const tier1Names = new Set(getCliToolDefinitions().map(t => t.name));
    const categoryTools = getCliToolDefinitionsForCategory(category)
      .filter(t => !tier1Names.has(t.name));
    if (categoryTools.length === 0) return;

    this.activatedCategories.push(category);
    this.dynamicToolDefinitions.push(...categoryTools);

    // Enforce accumulation cap by removing oldest category's tools
    while (this.dynamicToolDefinitions.length > MAX_DYNAMIC_TOOLS && this.activatedCategories.length > 1) {
      const oldestCategory = this.activatedCategories.shift()!;
      this.dynamicToolDefinitions = this.dynamicToolDefinitions.filter(
        t => !getCliToolDefinitionsForCategory(oldestCategory).some(ct => ct.name === t.name)
      );
    }

    this.server.sendToolListChanged().catch(() => {});
  }

  async loadCommands(): Promise<void> {
    try {
      const indexPath = join(__dirname, '..', '..', 'bin', 'index.js');
      const indexUrl = pathToFileURL(indexPath).href;
      const indexModule = await import(indexUrl);

      if (typeof indexModule.init !== 'function') {
        throw new Error('index.js does not export an init() function');
      }

      const commands = await indexModule.init();

      if (!Array.isArray(commands)) {
        throw new Error('init() did not return an array of commands');
      }

      console.error(`[MCP] Loaded ${commands.length} command modules from hana-cli`);

      for (const cmd of commands) {
        if (cmd && typeof cmd.command === 'string') {
          const commandName = cmd.command.split(' ')[0];
          this.commands.set(commandName, cmd);
        }
      }

      console.error(`[MCP] Registered ${this.commands.size} unique commands`);

      if (this.commands.size === 0) {
        throw new Error('No valid commands were registered');
      }

      initCliTools(this.commands);
      initRouterTool(this.commands);
    } catch (error) {
      const errorMsg = error instanceof Error ? error.message : String(error);
      console.error(`[MCP] Failed to load commands: ${errorMsg}`);
      throw error;
    }
  }

  async run(): Promise<void> {
    await this.loadCommands();
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('[MCP] SAP HANA CLI MCP Server running on stdio');
  }
}

const server = new HanaCliMcpServer();
server.run().catch((error) => {
  console.error('[MCP] Fatal error:', error);
  process.exit(1);
});
