/**
 * MCP Server with lazy tool loading
 * Starts with core tools, dynamically registers categories on demand
 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ErrorCode
} from '@modelcontextprotocol/sdk/types.js';
import { zodToJsonSchema } from 'zod-to-json-schema';

import type { ToolDefinition, ToolModule } from './tools/types.js';
import type { GSCClient } from './lib/gsc-client.js';
import type { Cache } from './lib/cache.js';
import type { RateLimiter } from './lib/rate-limit.js';

// Import always-registered tools
import discoveryTools from './tools/discovery.js';

export interface ServerConfig {
  gscClient: GSCClient;
  cache: Cache;
  rateLimiter: RateLimiter;
}

export class GSCServer {
  private server: Server;
  private gscClient: GSCClient;
  private cache: Cache;
  private rateLimiter: RateLimiter;
  
  // Tool registry
  private toolRegistry = new Map<string, ToolDefinition>();
  private registeredCategories = new Set<string>();

  constructor(config: ServerConfig) {
    this.gscClient = config.gscClient;
    this.cache = config.cache;
    this.rateLimiter = config.rateLimiter;

    this.server = new Server(
      {
        name: 'google-search-console-mcp',
        version: '1.0.0'
      },
      {
        capabilities: {
          tools: {}
        }
      }
    );

    // Register always-available discovery tools
    this.registerCategory('discovery', discoveryTools);

    this.setupHandlers();
  }

  /**
   * Register a tool category
   */
  private registerCategory(category: string, module: ToolModule): void {
    if (this.registeredCategories.has(category)) {
      return; // Already registered
    }

    for (const tool of module.tools) {
      this.toolRegistry.set(tool.name, tool);
    }

    this.registeredCategories.add(category);
    console.error(`Registered category: ${category} (${module.tools.length} tools)`);
  }

  /**
   * Lazy load a tool category
   */
  private async loadCategory(category: string): Promise<void> {
    if (this.registeredCategories.has(category)) {
      return; // Already loaded
    }

    console.error(`Lazy loading category: ${category}`);

    try {
      let module: ToolModule;

      switch (category) {
        case 'analytics':
          module = await import('./tools/analytics.js');
          break;
        case 'intelligence':
          // Will be implemented in phase 2
          throw new Error('Intelligence tools not yet implemented');
        case 'indexing':
          // Will be implemented in phase 2
          throw new Error('Indexing tools not yet implemented');
        case 'sitemaps':
          // Will be implemented in phase 2
          throw new Error('Sitemaps tools not yet implemented');
        case 'management':
          // Will be implemented in phase 2
          throw new Error('Management tools not yet implemented');
        default:
          throw new Error(`Unknown category: ${category}`);
      }

      this.registerCategory(category, (module as any).default || module);

      // Send tools/list_changed notification
      await this.server.notification({
        method: 'notifications/tools/list_changed',
        params: {}
      });
    } catch (error: any) {
      console.error(`Failed to load category ${category}:`, error);
      throw error;
    }
  }

  /**
   * Setup MCP protocol handlers
   */
  private setupHandlers(): void {
    // List tools handler
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      const tools = Array.from(this.toolRegistry.values()).map(tool => ({
        name: tool.name,
        description: tool.description,
        inputSchema: zodToJsonSchema(tool.inputSchema),
        _meta: tool._meta
      }));

      return { tools };
    });

    // Call tool handler
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const toolName = request.params.name;
      const tool = this.toolRegistry.get(toolName);

      if (!tool) {
        // Check if this is a discover_tools call that should trigger lazy loading
        if (toolName === 'discover_tools' && request.params.arguments?.category) {
          const category = request.params.arguments.category as string;
          try {
            await this.loadCategory(category);
            // Re-execute discover_tools to show the new tools
            const discoveryTool = this.toolRegistry.get('discover_tools');
            if (discoveryTool) {
              const result = await discoveryTool.handler(request.params.arguments, this.gscClient);
              return {
                content: result.content,
                isError: result.isError || false
              };
            }
          } catch (error: any) {
            return {
              content: [{
                type: 'text' as const,
                text: `Failed to load category: ${error.message}`
              }],
              isError: true
            };
          }
        }

        throw new McpError(
          ErrorCode.MethodNotFound,
          `Tool not found: ${toolName}. Use discover_tools to see available tools.`
        );
      }

      try {
        // Parse and validate arguments
        const args = tool.inputSchema.parse(request.params.arguments || {});

        // Check cache for read-only operations
        if (tool.annotations.readOnlyHint) {
          const cached = this.cache.get<{ content: any[]; isError?: boolean }>(toolName, args);
          if (cached) {
            console.error(`Cache hit: ${toolName}`);
            return {
              content: cached.content,
              isError: cached.isError || false
            };
          }
        }

        // Acquire rate limit token
        const apiType = this.getAPIType(toolName);
        await this.rateLimiter.acquire(apiType);

        // Execute tool
        console.error(`Executing tool: ${toolName}`);
        const result = await tool.handler(args, this.gscClient);

        // Cache read-only results
        if (tool.annotations.readOnlyHint && !result.isError) {
          this.cache.set(toolName, args, result);
        }

        return {
          content: result.content,
          isError: result.isError
        };
      } catch (error: any) {
        console.error(`Tool execution error (${toolName}):`, error);
        
        if (error.name === 'ZodError') {
          return {
            content: [{
              type: 'text',
              text: `Invalid arguments: ${JSON.stringify(error.errors, null, 2)}`
            }],
            isError: true
          };
        }

        return {
          content: [{
            type: 'text',
            text: `Error: ${error.message}`
          }],
          isError: true
        };
      }
    });
  }

  /**
   * Determine API type for rate limiting
   */
  private getAPIType(toolName: string): 'gsc' | 'inspection' | 'indexing' {
    if (toolName.includes('inspect')) {
      return 'inspection';
    }
    if (toolName.includes('indexing') || toolName === 'request_indexing') {
      return 'indexing';
    }
    return 'gsc';
  }

  /**
   * Connect server to stdio transport
   */
  async connect(): Promise<void> {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Google Search Console MCP server running on stdio');
  }

  /**
   * Get server instance for testing
   */
  getServer(): Server {
    return this.server;
  }
}
